Merge branch 'feature/file_transfer' into development
Conflicts: src/main/res/values/strings.xml
This commit is contained in:
commit
35bf13f5ef
|
@ -19,6 +19,9 @@ public final class Config {
|
||||||
public static final int MESSAGE_MERGE_WINDOW = 20;
|
public static final int MESSAGE_MERGE_WINDOW = 20;
|
||||||
|
|
||||||
public static final boolean PARSE_EMOTICONS = false;
|
public static final boolean PARSE_EMOTICONS = false;
|
||||||
|
public static final int PROGRESS_UI_UPDATE_INTERVAL = 750;
|
||||||
|
|
||||||
|
public static final boolean NO_PROXY_LOOKUP = false; //useful to debug ibb
|
||||||
|
|
||||||
private Config() {
|
private Config() {
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ package eu.siacs.conversations.crypto;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -24,7 +23,6 @@ import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.ui.UiCallback;
|
import eu.siacs.conversations.ui.UiCallback;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.BitmapFactory;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
public class PgpEngine {
|
public class PgpEngine {
|
||||||
|
@ -80,12 +78,13 @@ public class PgpEngine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (message.getType() == Message.TYPE_IMAGE) {
|
} else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
|
||||||
try {
|
try {
|
||||||
final DownloadableFile inputFile = this.mXmppConnectionService
|
final DownloadableFile inputFile = this.mXmppConnectionService
|
||||||
.getFileBackend().getFile(message, false);
|
.getFileBackend().getFile(message, false);
|
||||||
final DownloadableFile outputFile = this.mXmppConnectionService
|
final DownloadableFile outputFile = this.mXmppConnectionService
|
||||||
.getFileBackend().getFile(message, true);
|
.getFileBackend().getFile(message, true);
|
||||||
|
outputFile.getParentFile().mkdirs();
|
||||||
outputFile.createNewFile();
|
outputFile.createNewFile();
|
||||||
InputStream is = new FileInputStream(inputFile);
|
InputStream is = new FileInputStream(inputFile);
|
||||||
OutputStream os = new FileOutputStream(outputFile);
|
OutputStream os = new FileOutputStream(outputFile);
|
||||||
|
@ -97,24 +96,7 @@ public class PgpEngine {
|
||||||
OpenPgpApi.RESULT_CODE_ERROR)) {
|
OpenPgpApi.RESULT_CODE_ERROR)) {
|
||||||
case OpenPgpApi.RESULT_CODE_SUCCESS:
|
case OpenPgpApi.RESULT_CODE_SUCCESS:
|
||||||
URL url = message.getImageParams().url;
|
URL url = message.getImageParams().url;
|
||||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
mXmppConnectionService.getFileBackend().updateFileParams(message,url);
|
||||||
options.inJustDecodeBounds = true;
|
|
||||||
BitmapFactory.decodeFile(
|
|
||||||
outputFile.getAbsolutePath(), options);
|
|
||||||
int imageHeight = options.outHeight;
|
|
||||||
int imageWidth = options.outWidth;
|
|
||||||
if (url == null) {
|
|
||||||
message.setBody(Long.toString(outputFile
|
|
||||||
.getSize())
|
|
||||||
+ '|'
|
|
||||||
+ imageWidth
|
|
||||||
+ '|'
|
|
||||||
+ imageHeight);
|
|
||||||
} else {
|
|
||||||
message.setBody(url.toString() + "|"
|
|
||||||
+ Long.toString(outputFile.getSize())
|
|
||||||
+ '|' + imageWidth + '|' + imageHeight);
|
|
||||||
}
|
|
||||||
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
|
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
|
||||||
PgpEngine.this.mXmppConnectionService
|
PgpEngine.this.mXmppConnectionService
|
||||||
.updateMessage(message);
|
.updateMessage(message);
|
||||||
|
@ -199,12 +181,13 @@ public class PgpEngine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (message.getType() == Message.TYPE_IMAGE) {
|
} else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
|
||||||
try {
|
try {
|
||||||
DownloadableFile inputFile = this.mXmppConnectionService
|
DownloadableFile inputFile = this.mXmppConnectionService
|
||||||
.getFileBackend().getFile(message, true);
|
.getFileBackend().getFile(message, true);
|
||||||
DownloadableFile outputFile = this.mXmppConnectionService
|
DownloadableFile outputFile = this.mXmppConnectionService
|
||||||
.getFileBackend().getFile(message, false);
|
.getFileBackend().getFile(message, false);
|
||||||
|
outputFile.getParentFile().mkdirs();
|
||||||
outputFile.createNewFile();
|
outputFile.createNewFile();
|
||||||
InputStream is = new FileInputStream(inputFile);
|
InputStream is = new FileInputStream(inputFile);
|
||||||
OutputStream os = new FileOutputStream(outputFile);
|
OutputStream os = new FileOutputStream(outputFile);
|
||||||
|
|
|
@ -2,7 +2,7 @@ package eu.siacs.conversations.entities;
|
||||||
|
|
||||||
public interface Downloadable {
|
public interface Downloadable {
|
||||||
|
|
||||||
public final String[] VALID_EXTENSIONS = {"webp", "jpeg", "jpg", "png", "jpe"};
|
public final String[] VALID_IMAGE_EXTENSIONS = {"webp", "jpeg", "jpg", "png", "jpe"};
|
||||||
public final String[] VALID_CRYPTO_EXTENSIONS = {"pgp", "gpg", "otr"};
|
public final String[] VALID_CRYPTO_EXTENSIONS = {"pgp", "gpg", "otr"};
|
||||||
|
|
||||||
public static final int STATUS_UNKNOWN = 0x200;
|
public static final int STATUS_UNKNOWN = 0x200;
|
||||||
|
@ -12,10 +12,17 @@ public interface Downloadable {
|
||||||
public static final int STATUS_DOWNLOADING = 0x204;
|
public static final int STATUS_DOWNLOADING = 0x204;
|
||||||
public static final int STATUS_DELETED = 0x205;
|
public static final int STATUS_DELETED = 0x205;
|
||||||
public static final int STATUS_OFFER_CHECK_FILESIZE = 0x206;
|
public static final int STATUS_OFFER_CHECK_FILESIZE = 0x206;
|
||||||
|
public static final int STATUS_UPLOADING = 0x207;
|
||||||
|
|
||||||
public boolean start();
|
public boolean start();
|
||||||
|
|
||||||
public int getStatus();
|
public int getStatus();
|
||||||
|
|
||||||
public long getFileSize();
|
public long getFileSize();
|
||||||
|
|
||||||
|
public int getProgress();
|
||||||
|
|
||||||
|
public String getMimeType();
|
||||||
|
|
||||||
|
public void cancel();
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.net.URLConnection;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
|
@ -28,6 +29,7 @@ public class DownloadableFile extends File {
|
||||||
private long expectedSize = 0;
|
private long expectedSize = 0;
|
||||||
private String sha1sum;
|
private String sha1sum;
|
||||||
private Key aeskey;
|
private Key aeskey;
|
||||||
|
private String mime;
|
||||||
|
|
||||||
private byte[] iv = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
private byte[] iv = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf };
|
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf };
|
||||||
|
@ -52,6 +54,18 @@ public class DownloadableFile extends File {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getMimeType() {
|
||||||
|
String path = this.getAbsolutePath();
|
||||||
|
String mime = URLConnection.guessContentTypeFromName(path);
|
||||||
|
if (mime != null) {
|
||||||
|
return mime;
|
||||||
|
} else if (mime == null && path.endsWith(".webp")) {
|
||||||
|
return "image/webp";
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setExpectedSize(long size) {
|
public void setExpectedSize(long size) {
|
||||||
this.expectedSize = size;
|
this.expectedSize = size;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
package eu.siacs.conversations.entities;
|
||||||
|
|
||||||
|
public class DownloadablePlaceholder implements Downloadable {
|
||||||
|
|
||||||
|
private int status;
|
||||||
|
|
||||||
|
public DownloadablePlaceholder(int status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean start() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getFileSize() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getProgress() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMimeType() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancel() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,7 +32,7 @@ public class Message extends AbstractEntity {
|
||||||
|
|
||||||
public static final int TYPE_TEXT = 0;
|
public static final int TYPE_TEXT = 0;
|
||||||
public static final int TYPE_IMAGE = 1;
|
public static final int TYPE_IMAGE = 1;
|
||||||
public static final int TYPE_AUDIO = 2;
|
public static final int TYPE_FILE = 2;
|
||||||
public static final int TYPE_STATUS = 3;
|
public static final int TYPE_STATUS = 3;
|
||||||
public static final int TYPE_PRIVATE = 4;
|
public static final int TYPE_PRIVATE = 4;
|
||||||
|
|
||||||
|
@ -45,6 +45,7 @@ public class Message extends AbstractEntity {
|
||||||
public static String STATUS = "status";
|
public static String STATUS = "status";
|
||||||
public static String TYPE = "type";
|
public static String TYPE = "type";
|
||||||
public static String REMOTE_MSG_ID = "remoteMsgId";
|
public static String REMOTE_MSG_ID = "remoteMsgId";
|
||||||
|
public static String RELATIVE_FILE_PATH = "relativeFilePath";
|
||||||
public boolean markable = false;
|
public boolean markable = false;
|
||||||
protected String conversationUuid;
|
protected String conversationUuid;
|
||||||
protected Jid counterpart;
|
protected Jid counterpart;
|
||||||
|
@ -55,6 +56,7 @@ public class Message extends AbstractEntity {
|
||||||
protected int encryption;
|
protected int encryption;
|
||||||
protected int status;
|
protected int status;
|
||||||
protected int type;
|
protected int type;
|
||||||
|
protected String relativeFilePath;
|
||||||
protected boolean read = true;
|
protected boolean read = true;
|
||||||
protected String remoteMsgId = null;
|
protected String remoteMsgId = null;
|
||||||
protected Conversation conversation = null;
|
protected Conversation conversation = null;
|
||||||
|
@ -74,13 +76,13 @@ public class Message extends AbstractEntity {
|
||||||
this(java.util.UUID.randomUUID().toString(), conversation.getUuid(),
|
this(java.util.UUID.randomUUID().toString(), conversation.getUuid(),
|
||||||
conversation.getContactJid().toBareJid(), null, body, System
|
conversation.getContactJid().toBareJid(), null, body, System
|
||||||
.currentTimeMillis(), encryption,
|
.currentTimeMillis(), encryption,
|
||||||
status, TYPE_TEXT, null);
|
status, TYPE_TEXT, null,null);
|
||||||
this.conversation = conversation;
|
this.conversation = conversation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Message(final String uuid, final String conversationUUid, final Jid counterpart,
|
public Message(final String uuid, final String conversationUUid, final Jid counterpart,
|
||||||
final String trueCounterpart, final String body, final long timeSent,
|
final String trueCounterpart, final String body, final long timeSent,
|
||||||
final int encryption, final int status, final int type, final String remoteMsgId) {
|
final int encryption, final int status, final int type, final String remoteMsgId, final String relativeFilePath) {
|
||||||
this.uuid = uuid;
|
this.uuid = uuid;
|
||||||
this.conversationUuid = conversationUUid;
|
this.conversationUuid = conversationUUid;
|
||||||
this.counterpart = counterpart;
|
this.counterpart = counterpart;
|
||||||
|
@ -91,6 +93,7 @@ public class Message extends AbstractEntity {
|
||||||
this.status = status;
|
this.status = status;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.remoteMsgId = remoteMsgId;
|
this.remoteMsgId = remoteMsgId;
|
||||||
|
this.relativeFilePath = relativeFilePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Message fromCursor(Cursor cursor) {
|
public static Message fromCursor(Cursor cursor) {
|
||||||
|
@ -114,7 +117,8 @@ public class Message extends AbstractEntity {
|
||||||
cursor.getInt(cursor.getColumnIndex(ENCRYPTION)),
|
cursor.getInt(cursor.getColumnIndex(ENCRYPTION)),
|
||||||
cursor.getInt(cursor.getColumnIndex(STATUS)),
|
cursor.getInt(cursor.getColumnIndex(STATUS)),
|
||||||
cursor.getInt(cursor.getColumnIndex(TYPE)),
|
cursor.getInt(cursor.getColumnIndex(TYPE)),
|
||||||
cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)));
|
cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)),
|
||||||
|
cursor.getString(cursor.getColumnIndex(RELATIVE_FILE_PATH)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Message createStatusMessage(Conversation conversation) {
|
public static Message createStatusMessage(Conversation conversation) {
|
||||||
|
@ -141,6 +145,7 @@ public class Message extends AbstractEntity {
|
||||||
values.put(STATUS, status);
|
values.put(STATUS, status);
|
||||||
values.put(TYPE, type);
|
values.put(TYPE, type);
|
||||||
values.put(REMOTE_MSG_ID, remoteMsgId);
|
values.put(REMOTE_MSG_ID, remoteMsgId);
|
||||||
|
values.put(RELATIVE_FILE_PATH, relativeFilePath);
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,6 +210,14 @@ public class Message extends AbstractEntity {
|
||||||
this.status = status;
|
this.status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setRelativeFilePath(String path) {
|
||||||
|
this.relativeFilePath = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRelativeFilePath() {
|
||||||
|
return this.relativeFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
public String getRemoteMsgId() {
|
public String getRemoteMsgId() {
|
||||||
return this.remoteMsgId;
|
return this.remoteMsgId;
|
||||||
}
|
}
|
||||||
|
@ -376,14 +389,14 @@ public class Message extends AbstractEntity {
|
||||||
}
|
}
|
||||||
String[] extensionParts = filename.split("\\.");
|
String[] extensionParts = filename.split("\\.");
|
||||||
if (extensionParts.length == 2
|
if (extensionParts.length == 2
|
||||||
&& Arrays.asList(Downloadable.VALID_EXTENSIONS).contains(
|
&& Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains(
|
||||||
extensionParts[extensionParts.length - 1])) {
|
extensionParts[extensionParts.length - 1])) {
|
||||||
return true;
|
return true;
|
||||||
} else if (extensionParts.length == 3
|
} else if (extensionParts.length == 3
|
||||||
&& Arrays
|
&& Arrays
|
||||||
.asList(Downloadable.VALID_CRYPTO_EXTENSIONS)
|
.asList(Downloadable.VALID_CRYPTO_EXTENSIONS)
|
||||||
.contains(extensionParts[extensionParts.length - 1])
|
.contains(extensionParts[extensionParts.length - 1])
|
||||||
&& Arrays.asList(Downloadable.VALID_EXTENSIONS).contains(
|
&& Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains(
|
||||||
extensionParts[extensionParts.length - 2])) {
|
extensionParts[extensionParts.length - 2])) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package eu.siacs.conversations.http;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
|
||||||
import org.apache.http.conn.ssl.StrictHostnameVerifier;
|
import org.apache.http.conn.ssl.StrictHostnameVerifier;
|
||||||
|
|
||||||
|
@ -21,6 +22,7 @@ import javax.net.ssl.SSLContext;
|
||||||
import javax.net.ssl.SSLHandshakeException;
|
import javax.net.ssl.SSLHandshakeException;
|
||||||
import javax.net.ssl.X509TrustManager;
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.entities.Downloadable;
|
import eu.siacs.conversations.entities.Downloadable;
|
||||||
import eu.siacs.conversations.entities.DownloadableFile;
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
|
@ -37,6 +39,8 @@ public class HttpConnection implements Downloadable {
|
||||||
private DownloadableFile file;
|
private DownloadableFile file;
|
||||||
private int mStatus = Downloadable.STATUS_UNKNOWN;
|
private int mStatus = Downloadable.STATUS_UNKNOWN;
|
||||||
private boolean acceptedAutomatically = false;
|
private boolean acceptedAutomatically = false;
|
||||||
|
private int mProgress = 0;
|
||||||
|
private long mLastGuiRefresh = 0;
|
||||||
|
|
||||||
public HttpConnection(HttpConnectionManager manager) {
|
public HttpConnection(HttpConnectionManager manager) {
|
||||||
this.mHttpConnectionManager = manager;
|
this.mHttpConnectionManager = manager;
|
||||||
|
@ -235,10 +239,14 @@ public class HttpConnection implements Downloadable {
|
||||||
if (os == null) {
|
if (os == null) {
|
||||||
throw new IOException();
|
throw new IOException();
|
||||||
}
|
}
|
||||||
|
long transmitted = 0;
|
||||||
|
long expected = file.getExpectedSize();
|
||||||
int count = -1;
|
int count = -1;
|
||||||
byte[] buffer = new byte[1024];
|
byte[] buffer = new byte[1024];
|
||||||
while ((count = is.read(buffer)) != -1) {
|
while ((count = is.read(buffer)) != -1) {
|
||||||
|
transmitted += count;
|
||||||
os.write(buffer, 0, count);
|
os.write(buffer, 0, count);
|
||||||
|
updateProgress((int) ((((double) transmitted) / expected) * 100));
|
||||||
}
|
}
|
||||||
os.flush();
|
os.flush();
|
||||||
os.close();
|
os.close();
|
||||||
|
@ -246,19 +254,21 @@ public class HttpConnection implements Downloadable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateImageBounds() {
|
private void updateImageBounds() {
|
||||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
|
||||||
options.inJustDecodeBounds = true;
|
|
||||||
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
|
|
||||||
int imageHeight = options.outHeight;
|
|
||||||
int imageWidth = options.outWidth;
|
|
||||||
message.setBody(mUrl.toString() + "|" + file.getSize() + '|'
|
|
||||||
+ imageWidth + '|' + imageHeight);
|
|
||||||
message.setType(Message.TYPE_IMAGE);
|
message.setType(Message.TYPE_IMAGE);
|
||||||
|
mXmppConnectionService.getFileBackend().updateFileParams(message,mUrl);
|
||||||
mXmppConnectionService.updateMessage(message);
|
mXmppConnectionService.updateMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateProgress(int i) {
|
||||||
|
this.mProgress = i;
|
||||||
|
if (SystemClock.elapsedRealtime() - this.mLastGuiRefresh > Config.PROGRESS_UI_UPDATE_INTERVAL) {
|
||||||
|
this.mLastGuiRefresh = SystemClock.elapsedRealtime();
|
||||||
|
mXmppConnectionService.updateConversationUi();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getStatus() {
|
public int getStatus() {
|
||||||
return this.mStatus;
|
return this.mStatus;
|
||||||
|
@ -272,4 +282,14 @@ public class HttpConnection implements Downloadable {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getProgress() {
|
||||||
|
return this.mProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMimeType() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -22,7 +22,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
private static DatabaseBackend instance = null;
|
private static DatabaseBackend instance = null;
|
||||||
|
|
||||||
private static final String DATABASE_NAME = "history";
|
private static final String DATABASE_NAME = "history";
|
||||||
private static final int DATABASE_VERSION = 9;
|
private static final int DATABASE_VERSION = 10;
|
||||||
|
|
||||||
private static String CREATE_CONTATCS_STATEMENT = "create table "
|
private static String CREATE_CONTATCS_STATEMENT = "create table "
|
||||||
+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
|
+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
|
||||||
|
@ -64,6 +64,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
+ " TEXT, " + Message.TRUE_COUNTERPART + " TEXT,"
|
+ " TEXT, " + Message.TRUE_COUNTERPART + " TEXT,"
|
||||||
+ Message.BODY + " TEXT, " + Message.ENCRYPTION + " NUMBER, "
|
+ Message.BODY + " TEXT, " + Message.ENCRYPTION + " NUMBER, "
|
||||||
+ Message.STATUS + " NUMBER," + Message.TYPE + " NUMBER, "
|
+ Message.STATUS + " NUMBER," + Message.TYPE + " NUMBER, "
|
||||||
|
+ Message.RELATIVE_FILE_PATH + " TEXT, "
|
||||||
+ Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
|
+ Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
|
||||||
+ Message.CONVERSATION + ") REFERENCES "
|
+ Message.CONVERSATION + ") REFERENCES "
|
||||||
+ Conversation.TABLENAME + "(" + Conversation.UUID
|
+ Conversation.TABLENAME + "(" + Conversation.UUID
|
||||||
|
@ -110,6 +111,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
|
db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
|
||||||
+ Contact.LAST_PRESENCE + " TEXT");
|
+ Contact.LAST_PRESENCE + " TEXT");
|
||||||
}
|
}
|
||||||
|
if (oldVersion < 10 && newVersion >= 10) {
|
||||||
|
db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
|
||||||
|
+ Message.RELATIVE_FILE_PATH + " TEXT");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized DatabaseBackend getInstance(Context context) {
|
public static synchronized DatabaseBackend getInstance(Context context) {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.net.URL;
|
||||||
import java.security.DigestOutputStream;
|
import java.security.DigestOutputStream;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
@ -26,6 +27,8 @@ import android.provider.MediaStore;
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
import android.util.Base64OutputStream;
|
import android.util.Base64OutputStream;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.entities.DownloadableFile;
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
|
@ -53,25 +56,40 @@ public class FileBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
public DownloadableFile getFile(Message message, boolean decrypted) {
|
public DownloadableFile getFile(Message message, boolean decrypted) {
|
||||||
StringBuilder filename = new StringBuilder();
|
String path = message.getRelativeFilePath();
|
||||||
filename.append(getConversationsDirectory());
|
if (!decrypted && (message.getEncryption() == Message.ENCRYPTION_PGP || message.getEncryption() == Message.ENCRYPTION_DECRYPTED)) {
|
||||||
filename.append(message.getUuid());
|
String extension;
|
||||||
if ((decrypted) || (message.getEncryption() == Message.ENCRYPTION_NONE)) {
|
if (path != null && !path.isEmpty()) {
|
||||||
filename.append(".webp");
|
String[] parts = path.split("\\.");
|
||||||
} else {
|
extension = "."+parts[parts.length - 1];
|
||||||
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
} else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_TEXT) {
|
||||||
filename.append(".webp");
|
extension = ".webp";
|
||||||
} else {
|
} else {
|
||||||
filename.append(".webp.pgp");
|
extension = "";
|
||||||
}
|
}
|
||||||
|
return new DownloadableFile(getConversationsFileDirectory()+message.getUuid()+extension+".pgp");
|
||||||
|
} else if (path != null && !path.isEmpty()) {
|
||||||
|
if (path.startsWith("/")) {
|
||||||
|
return new DownloadableFile(path);
|
||||||
|
} else {
|
||||||
|
return new DownloadableFile(getConversationsFileDirectory()+path);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
StringBuilder filename = new StringBuilder();
|
||||||
|
filename.append(getConversationsImageDirectory());
|
||||||
|
filename.append(message.getUuid()+".webp");
|
||||||
|
return new DownloadableFile(filename.toString());
|
||||||
}
|
}
|
||||||
return new DownloadableFile(filename.toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getConversationsDirectory() {
|
public static String getConversationsFileDirectory() {
|
||||||
|
return Environment.getExternalStorageDirectory().getAbsolutePath()+"/Conversations/";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getConversationsImageDirectory() {
|
||||||
return Environment.getExternalStoragePublicDirectory(
|
return Environment.getExternalStoragePublicDirectory(
|
||||||
Environment.DIRECTORY_PICTURES).getAbsolutePath()
|
Environment.DIRECTORY_PICTURES).getAbsolutePath()
|
||||||
+ "/Conversations/";
|
+ "/Conversations/";
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bitmap resize(Bitmap originalBitmap, int size) {
|
public Bitmap resize(Bitmap originalBitmap, int size) {
|
||||||
|
@ -103,13 +121,60 @@ public class FileBackend {
|
||||||
return Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true);
|
return Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getOriginalPath(Uri uri) {
|
||||||
|
String path = null;
|
||||||
|
if (uri.getScheme().equals("file")) {
|
||||||
|
return uri.getPath();
|
||||||
|
} else if (uri.toString().startsWith("content://media/")) {
|
||||||
|
String[] projection = {MediaStore.MediaColumns.DATA};
|
||||||
|
Cursor metaCursor = mXmppConnectionService.getContentResolver().query(uri,
|
||||||
|
projection, null, null, null);
|
||||||
|
if (metaCursor != null) {
|
||||||
|
try {
|
||||||
|
if (metaCursor.moveToFirst()) {
|
||||||
|
path = metaCursor.getString(0);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
metaCursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadableFile copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException {
|
||||||
|
try {
|
||||||
|
Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage");
|
||||||
|
String mime = mXmppConnectionService.getContentResolver().getType(uri);
|
||||||
|
String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime);
|
||||||
|
message.setRelativeFilePath(message.getUuid() + "." + extension);
|
||||||
|
DownloadableFile file = mXmppConnectionService.getFileBackend().getFile(message);
|
||||||
|
OutputStream os = new FileOutputStream(file);
|
||||||
|
InputStream is = mXmppConnectionService.getContentResolver().openInputStream(uri);
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int length;
|
||||||
|
while ((length = is.read(buffer)) > 0) {
|
||||||
|
os.write(buffer, 0, length);
|
||||||
|
}
|
||||||
|
os.flush();
|
||||||
|
os.close();
|
||||||
|
is.close();
|
||||||
|
Log.d(Config.LOGTAG, "output file name " + mXmppConnectionService.getFileBackend().getFile(message));
|
||||||
|
return file;
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
throw new FileCopyException(R.string.error_file_not_found);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new FileCopyException(R.string.error_io_exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public DownloadableFile copyImageToPrivateStorage(Message message, Uri image)
|
public DownloadableFile copyImageToPrivateStorage(Message message, Uri image)
|
||||||
throws ImageCopyException {
|
throws FileCopyException {
|
||||||
return this.copyImageToPrivateStorage(message, image, 0);
|
return this.copyImageToPrivateStorage(message, image, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DownloadableFile copyImageToPrivateStorage(Message message,
|
private DownloadableFile copyImageToPrivateStorage(Message message,
|
||||||
Uri image, int sampleSize) throws ImageCopyException {
|
Uri image, int sampleSize) throws FileCopyException {
|
||||||
try {
|
try {
|
||||||
InputStream is = mXmppConnectionService.getContentResolver()
|
InputStream is = mXmppConnectionService.getContentResolver()
|
||||||
.openInputStream(image);
|
.openInputStream(image);
|
||||||
|
@ -125,7 +190,7 @@ public class FileBackend {
|
||||||
originalBitmap = BitmapFactory.decodeStream(is, null, options);
|
originalBitmap = BitmapFactory.decodeStream(is, null, options);
|
||||||
is.close();
|
is.close();
|
||||||
if (originalBitmap == null) {
|
if (originalBitmap == null) {
|
||||||
throw new ImageCopyException(R.string.error_not_an_image_file);
|
throw new FileCopyException(R.string.error_not_an_image_file);
|
||||||
}
|
}
|
||||||
Bitmap scalledBitmap = resize(originalBitmap, IMAGE_SIZE);
|
Bitmap scalledBitmap = resize(originalBitmap, IMAGE_SIZE);
|
||||||
originalBitmap = null;
|
originalBitmap = null;
|
||||||
|
@ -137,7 +202,7 @@ public class FileBackend {
|
||||||
boolean success = scalledBitmap.compress(
|
boolean success = scalledBitmap.compress(
|
||||||
Bitmap.CompressFormat.WEBP, 75, os);
|
Bitmap.CompressFormat.WEBP, 75, os);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
throw new ImageCopyException(R.string.error_compressing_image);
|
throw new FileCopyException(R.string.error_compressing_image);
|
||||||
}
|
}
|
||||||
os.flush();
|
os.flush();
|
||||||
os.close();
|
os.close();
|
||||||
|
@ -147,18 +212,18 @@ public class FileBackend {
|
||||||
message.setBody(Long.toString(size) + ',' + width + ',' + height);
|
message.setBody(Long.toString(size) + ',' + width + ',' + height);
|
||||||
return file;
|
return file;
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
throw new ImageCopyException(R.string.error_file_not_found);
|
throw new FileCopyException(R.string.error_file_not_found);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new ImageCopyException(R.string.error_io_exception);
|
throw new FileCopyException(R.string.error_io_exception);
|
||||||
} catch (SecurityException e) {
|
} catch (SecurityException e) {
|
||||||
throw new ImageCopyException(
|
throw new FileCopyException(
|
||||||
R.string.error_security_exception_during_image_copy);
|
R.string.error_security_exception_during_image_copy);
|
||||||
} catch (OutOfMemoryError e) {
|
} catch (OutOfMemoryError e) {
|
||||||
++sampleSize;
|
++sampleSize;
|
||||||
if (sampleSize <= 3) {
|
if (sampleSize <= 3) {
|
||||||
return copyImageToPrivateStorage(message, image, sampleSize);
|
return copyImageToPrivateStorage(message, image, sampleSize);
|
||||||
} else {
|
} else {
|
||||||
throw new ImageCopyException(R.string.error_out_of_memory);
|
throw new FileCopyException(R.string.error_out_of_memory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -400,11 +465,34 @@ public class FileBackend {
|
||||||
return Uri.parse("file://" + file.getAbsolutePath());
|
return Uri.parse("file://" + file.getAbsolutePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ImageCopyException extends Exception {
|
public void updateFileParams(Message message) {
|
||||||
|
updateFileParams(message,null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateFileParams(Message message, URL url) {
|
||||||
|
DownloadableFile file = getFile(message);
|
||||||
|
if (message.getType() == Message.TYPE_IMAGE || file.getMimeType().startsWith("image/")) {
|
||||||
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
|
options.inJustDecodeBounds = true;
|
||||||
|
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
|
||||||
|
int imageHeight = options.outHeight;
|
||||||
|
int imageWidth = options.outWidth;
|
||||||
|
if (url == null) {
|
||||||
|
message.setBody(Long.toString(file.getSize()) + '|' + imageWidth + '|' + imageHeight);
|
||||||
|
} else {
|
||||||
|
message.setBody(url.toString()+"|"+Long.toString(file.getSize()) + '|' + imageWidth + '|' + imageHeight);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message.setBody(Long.toString(file.getSize()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FileCopyException extends Exception {
|
||||||
private static final long serialVersionUID = -1010013599132881427L;
|
private static final long serialVersionUID = -1010013599132881427L;
|
||||||
private int resId;
|
private int resId;
|
||||||
|
|
||||||
public ImageCopyException(int resId) {
|
public FileCopyException(int resId) {
|
||||||
this.resId = resId;
|
this.resId = resId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
import eu.siacs.conversations.entities.Downloadable;
|
import eu.siacs.conversations.entities.Downloadable;
|
||||||
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.ui.ConversationActivity;
|
import eu.siacs.conversations.ui.ConversationActivity;
|
||||||
|
|
||||||
|
@ -267,14 +268,21 @@ public class NotificationService {
|
||||||
if (message.getDownloadable() != null
|
if (message.getDownloadable() != null
|
||||||
&& (message.getDownloadable().getStatus() == Downloadable.STATUS_OFFER || message
|
&& (message.getDownloadable().getStatus() == Downloadable.STATUS_OFFER || message
|
||||||
.getDownloadable().getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE)) {
|
.getDownloadable().getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE)) {
|
||||||
return mXmppConnectionService.getText(
|
if (message.getType() == Message.TYPE_FILE) {
|
||||||
R.string.image_offered_for_download).toString();
|
return mXmppConnectionService.getString(R.string.file_offered_for_download);
|
||||||
|
} else {
|
||||||
|
return mXmppConnectionService.getText(
|
||||||
|
R.string.image_offered_for_download).toString();
|
||||||
|
}
|
||||||
} else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
|
} else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
|
||||||
return mXmppConnectionService.getText(
|
return mXmppConnectionService.getText(
|
||||||
R.string.encrypted_message_received).toString();
|
R.string.encrypted_message_received).toString();
|
||||||
} else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
|
} else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
|
||||||
return mXmppConnectionService.getText(R.string.decryption_failed)
|
return mXmppConnectionService.getText(R.string.decryption_failed)
|
||||||
.toString();
|
.toString();
|
||||||
|
} else if (message.getType() == Message.TYPE_FILE) {
|
||||||
|
DownloadableFile file = mXmppConnectionService.getFileBackend().getFile(message);
|
||||||
|
return mXmppConnectionService.getString(R.string.file,file.getMimeType());
|
||||||
} else if (message.getType() == Message.TYPE_IMAGE) {
|
} else if (message.getType() == Message.TYPE_IMAGE) {
|
||||||
return mXmppConnectionService.getText(R.string.image_file)
|
return mXmppConnectionService.getText(R.string.image_file)
|
||||||
.toString();
|
.toString();
|
||||||
|
|
|
@ -56,6 +56,8 @@ import eu.siacs.conversations.entities.Bookmark;
|
||||||
import eu.siacs.conversations.entities.Contact;
|
import eu.siacs.conversations.entities.Contact;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
import eu.siacs.conversations.entities.Downloadable;
|
import eu.siacs.conversations.entities.Downloadable;
|
||||||
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
|
import eu.siacs.conversations.entities.DownloadablePlaceholder;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.entities.MucOptions;
|
import eu.siacs.conversations.entities.MucOptions;
|
||||||
import eu.siacs.conversations.entities.MucOptions.OnRenameListener;
|
import eu.siacs.conversations.entities.MucOptions.OnRenameListener;
|
||||||
|
@ -211,7 +213,7 @@ public class XmppConnectionService extends Service {
|
||||||
private Integer rosterChangedListenerCount = 0;
|
private Integer rosterChangedListenerCount = 0;
|
||||||
private SecureRandom mRandom;
|
private SecureRandom mRandom;
|
||||||
private FileObserver fileObserver = new FileObserver(
|
private FileObserver fileObserver = new FileObserver(
|
||||||
FileBackend.getConversationsDirectory()) {
|
FileBackend.getConversationsImageDirectory()) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEvent(int event, String path) {
|
public void onEvent(int event, String path) {
|
||||||
|
@ -295,7 +297,49 @@ public class XmppConnectionService extends Service {
|
||||||
return this.mAvatarService;
|
return this.mAvatarService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Message attachImageToConversation(final Conversation conversation,
|
public void attachFileToConversation(Conversation conversation, final Uri uri, final UiCallback<Message> callback) {
|
||||||
|
final Message message;
|
||||||
|
if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) {
|
||||||
|
message = new Message(conversation, "",
|
||||||
|
Message.ENCRYPTION_DECRYPTED);
|
||||||
|
} else {
|
||||||
|
message = new Message(conversation, "",
|
||||||
|
conversation.getNextEncryption(forceEncryption()));
|
||||||
|
}
|
||||||
|
message.setCounterpart(conversation.getNextCounterpart());
|
||||||
|
message.setType(Message.TYPE_FILE);
|
||||||
|
message.setStatus(Message.STATUS_OFFERED);
|
||||||
|
String path = getFileBackend().getOriginalPath(uri);
|
||||||
|
if (path!=null) {
|
||||||
|
message.setRelativeFilePath(path);
|
||||||
|
getFileBackend().updateFileParams(message);
|
||||||
|
if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
|
||||||
|
getPgpEngine().encrypt(message, callback);
|
||||||
|
} else {
|
||||||
|
callback.success(message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
getFileBackend().copyFileToPrivateStorage(message, uri);
|
||||||
|
getFileBackend().updateFileParams(message);
|
||||||
|
if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
|
||||||
|
getPgpEngine().encrypt(message, callback);
|
||||||
|
} else {
|
||||||
|
callback.success(message);
|
||||||
|
}
|
||||||
|
} catch (FileBackend.FileCopyException e) {
|
||||||
|
callback.error(e.getResId(),message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void attachImageToConversation(final Conversation conversation,
|
||||||
final Uri uri, final UiCallback<Message> callback) {
|
final Uri uri, final UiCallback<Message> callback) {
|
||||||
final Message message;
|
final Message message;
|
||||||
if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) {
|
if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) {
|
||||||
|
@ -313,18 +357,17 @@ public class XmppConnectionService extends Service {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
getFileBackend().copyImageToPrivateStorage(message, uri);
|
DownloadableFile file = getFileBackend().copyImageToPrivateStorage(message, uri);
|
||||||
if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) {
|
if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) {
|
||||||
getPgpEngine().encrypt(message, callback);
|
getPgpEngine().encrypt(message, callback);
|
||||||
} else {
|
} else {
|
||||||
callback.success(message);
|
callback.success(message);
|
||||||
}
|
}
|
||||||
} catch (FileBackend.ImageCopyException e) {
|
} catch (FileBackend.FileCopyException e) {
|
||||||
callback.error(e.getResId(), message);
|
callback.error(e.getResId(), message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
return message;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Conversation find(Bookmark bookmark) {
|
public Conversation find(Bookmark bookmark) {
|
||||||
|
@ -561,7 +604,7 @@ public class XmppConnectionService extends Service {
|
||||||
boolean send = false;
|
boolean send = false;
|
||||||
if (account.getStatus() == Account.STATUS_ONLINE
|
if (account.getStatus() == Account.STATUS_ONLINE
|
||||||
&& account.getXmppConnection() != null) {
|
&& account.getXmppConnection() != null) {
|
||||||
if (message.getType() == Message.TYPE_IMAGE) {
|
if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
|
||||||
if (message.getCounterpart() != null) {
|
if (message.getCounterpart() != null) {
|
||||||
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
||||||
if (!conv.hasValidOtrSession()) {
|
if (!conv.hasValidOtrSession()) {
|
||||||
|
@ -678,11 +721,16 @@ public class XmppConnectionService extends Service {
|
||||||
} else {
|
} else {
|
||||||
if (message.getConversation().getOtrSession()
|
if (message.getConversation().getOtrSession()
|
||||||
.getSessionStatus() == SessionStatus.ENCRYPTED) {
|
.getSessionStatus() == SessionStatus.ENCRYPTED) {
|
||||||
if (message.getType() == Message.TYPE_TEXT) {
|
try {
|
||||||
packet = mMessageGenerator.generateOtrChat(message,
|
message.setCounterpart(Jid.fromSessionID(message.getConversation().getOtrSession().getSessionID()));
|
||||||
true);
|
if (message.getType() == Message.TYPE_TEXT) {
|
||||||
} else if (message.getType() == Message.TYPE_IMAGE) {
|
packet = mMessageGenerator.generateOtrChat(message,
|
||||||
mJingleConnectionManager.createNewConnection(message);
|
true);
|
||||||
|
} else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
|
||||||
|
mJingleConnectionManager.createNewConnection(message);
|
||||||
|
}
|
||||||
|
} catch (InvalidJidException e) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -693,7 +741,7 @@ public class XmppConnectionService extends Service {
|
||||||
|| (message.getEncryption() == Message.ENCRYPTION_PGP)) {
|
|| (message.getEncryption() == Message.ENCRYPTION_PGP)) {
|
||||||
packet = mMessageGenerator.generatePgpChat(message, true);
|
packet = mMessageGenerator.generatePgpChat(message, true);
|
||||||
}
|
}
|
||||||
} else if (message.getType() == Message.TYPE_IMAGE) {
|
} else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
|
||||||
Contact contact = message.getConversation().getContact();
|
Contact contact = message.getConversation().getContact();
|
||||||
Presences presences = contact.getPresences();
|
Presences presences = contact.getPresences();
|
||||||
if ((message.getCounterpart() != null)
|
if ((message.getCounterpart() != null)
|
||||||
|
@ -852,10 +900,10 @@ public class XmppConnectionService extends Service {
|
||||||
|
|
||||||
private void checkDeletedFiles(Conversation conversation) {
|
private void checkDeletedFiles(Conversation conversation) {
|
||||||
for (Message message : conversation.getMessages()) {
|
for (Message message : conversation.getMessages()) {
|
||||||
if (message.getType() == Message.TYPE_IMAGE
|
if ((message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE)
|
||||||
&& message.getEncryption() != Message.ENCRYPTION_PGP) {
|
&& message.getEncryption() != Message.ENCRYPTION_PGP) {
|
||||||
if (!getFileBackend().isFileAvailable(message)) {
|
if (!getFileBackend().isFileAvailable(message)) {
|
||||||
message.setDownloadable(new DeletedDownloadable());
|
message.setDownloadable(new DownloadablePlaceholder(Downloadable.STATUS_DELETED));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -868,7 +916,7 @@ public class XmppConnectionService extends Service {
|
||||||
&& message.getEncryption() != Message.ENCRYPTION_PGP
|
&& message.getEncryption() != Message.ENCRYPTION_PGP
|
||||||
&& message.getUuid().equals(uuid)) {
|
&& message.getUuid().equals(uuid)) {
|
||||||
if (!getFileBackend().isFileAvailable(message)) {
|
if (!getFileBackend().isFileAvailable(message)) {
|
||||||
message.setDownloadable(new DeletedDownloadable());
|
message.setDownloadable(new DownloadablePlaceholder(Downloadable.STATUS_DELETED));
|
||||||
updateConversationUi();
|
updateConversationUi();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -1424,7 +1472,7 @@ public class XmppConnectionService extends Service {
|
||||||
databaseBackend.updateMessage(msg);
|
databaseBackend.updateMessage(msg);
|
||||||
sendMessagePacket(account, outPacket);
|
sendMessagePacket(account, outPacket);
|
||||||
}
|
}
|
||||||
} else if (msg.getType() == Message.TYPE_IMAGE) {
|
} else if (msg.getType() == Message.TYPE_IMAGE || msg.getType() == Message.TYPE_FILE) {
|
||||||
mJingleConnectionManager.createNewConnection(msg);
|
mJingleConnectionManager.createNewConnection(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1979,23 +2027,4 @@ public class XmppConnectionService extends Service {
|
||||||
return XmppConnectionService.this;
|
return XmppConnectionService.this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DeletedDownloadable implements Downloadable {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean start() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getStatus() {
|
|
||||||
return Downloadable.STATUS_DELETED;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getFileSize() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,13 +52,14 @@ public class ConversationActivity extends XmppActivity implements
|
||||||
public static final int REQUEST_SEND_MESSAGE = 0x0201;
|
public static final int REQUEST_SEND_MESSAGE = 0x0201;
|
||||||
public static final int REQUEST_DECRYPT_PGP = 0x0202;
|
public static final int REQUEST_DECRYPT_PGP = 0x0202;
|
||||||
public static final int REQUEST_ENCRYPT_MESSAGE = 0x0207;
|
public static final int REQUEST_ENCRYPT_MESSAGE = 0x0207;
|
||||||
private static final int REQUEST_ATTACH_FILE_DIALOG = 0x0203;
|
private static final int REQUEST_ATTACH_IMAGE_DIALOG = 0x0203;
|
||||||
private static final int REQUEST_IMAGE_CAPTURE = 0x0204;
|
private static final int REQUEST_IMAGE_CAPTURE = 0x0204;
|
||||||
private static final int REQUEST_RECORD_AUDIO = 0x0205;
|
private static final int REQUEST_RECORD_AUDIO = 0x0205;
|
||||||
private static final int REQUEST_SEND_PGP_IMAGE = 0x0206;
|
private static final int REQUEST_SEND_PGP_IMAGE = 0x0206;
|
||||||
|
private static final int REQUEST_ATTACH_FILE_DIALOG = 0x0208;
|
||||||
private static final int ATTACHMENT_CHOICE_CHOOSE_IMAGE = 0x0301;
|
private static final int ATTACHMENT_CHOICE_CHOOSE_IMAGE = 0x0301;
|
||||||
private static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302;
|
private static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302;
|
||||||
private static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0303;
|
private static final int ATTACHMENT_CHOICE_CHOOSE_FILE = 0x0303;
|
||||||
private static final String STATE_OPEN_CONVERSATION = "state_open_conversation";
|
private static final String STATE_OPEN_CONVERSATION = "state_open_conversation";
|
||||||
private static final String STATE_PANEL_OPEN = "state_panel_open";
|
private static final String STATE_PANEL_OPEN = "state_panel_open";
|
||||||
private static final String STATE_PENDING_URI = "state_pending_uri";
|
private static final String STATE_PENDING_URI = "state_pending_uri";
|
||||||
|
@ -66,6 +67,7 @@ public class ConversationActivity extends XmppActivity implements
|
||||||
private String mOpenConverstaion = null;
|
private String mOpenConverstaion = null;
|
||||||
private boolean mPanelOpen = true;
|
private boolean mPanelOpen = true;
|
||||||
private Uri mPendingImageUri = null;
|
private Uri mPendingImageUri = null;
|
||||||
|
private Uri mPendingFileUri = null;
|
||||||
|
|
||||||
private View mContentView;
|
private View mContentView;
|
||||||
|
|
||||||
|
@ -76,7 +78,7 @@ public class ConversationActivity extends XmppActivity implements
|
||||||
|
|
||||||
private ArrayAdapter<Conversation> listAdapter;
|
private ArrayAdapter<Conversation> listAdapter;
|
||||||
|
|
||||||
private Toast prepareImageToast;
|
private Toast prepareFileToast;
|
||||||
|
|
||||||
|
|
||||||
public List<Conversation> getConversationList() {
|
public List<Conversation> getConversationList() {
|
||||||
|
@ -306,13 +308,18 @@ public class ConversationActivity extends XmppActivity implements
|
||||||
Intent attachFileIntent = new Intent();
|
Intent attachFileIntent = new Intent();
|
||||||
attachFileIntent.setType("image/*");
|
attachFileIntent.setType("image/*");
|
||||||
attachFileIntent.setAction(Intent.ACTION_GET_CONTENT);
|
attachFileIntent.setAction(Intent.ACTION_GET_CONTENT);
|
||||||
|
Intent chooser = Intent.createChooser(attachFileIntent,
|
||||||
|
getString(R.string.attach_file));
|
||||||
|
startActivityForResult(chooser, REQUEST_ATTACH_IMAGE_DIALOG);
|
||||||
|
} else if (attachmentChoice == ATTACHMENT_CHOICE_CHOOSE_FILE) {
|
||||||
|
Intent attachFileIntent = new Intent();
|
||||||
|
//attachFileIntent.setType("file/*");
|
||||||
|
attachFileIntent.setType("*/*");
|
||||||
|
attachFileIntent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
attachFileIntent.setAction(Intent.ACTION_GET_CONTENT);
|
||||||
Intent chooser = Intent.createChooser(attachFileIntent,
|
Intent chooser = Intent.createChooser(attachFileIntent,
|
||||||
getString(R.string.attach_file));
|
getString(R.string.attach_file));
|
||||||
startActivityForResult(chooser, REQUEST_ATTACH_FILE_DIALOG);
|
startActivityForResult(chooser, REQUEST_ATTACH_FILE_DIALOG);
|
||||||
} else if (attachmentChoice == ATTACHMENT_CHOICE_RECORD_VOICE) {
|
|
||||||
Intent intent = new Intent(
|
|
||||||
MediaStore.Audio.Media.RECORD_SOUND_ACTION);
|
|
||||||
startActivityForResult(intent, REQUEST_RECORD_AUDIO);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -483,7 +490,7 @@ public class ConversationActivity extends XmppActivity implements
|
||||||
attachFile(ATTACHMENT_CHOICE_TAKE_PHOTO);
|
attachFile(ATTACHMENT_CHOICE_TAKE_PHOTO);
|
||||||
break;
|
break;
|
||||||
case R.id.attach_record_voice:
|
case R.id.attach_record_voice:
|
||||||
attachFile(ATTACHMENT_CHOICE_RECORD_VOICE);
|
attachFile(ATTACHMENT_CHOICE_CHOOSE_FILE);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -675,14 +682,17 @@ public class ConversationActivity extends XmppActivity implements
|
||||||
} else {
|
} else {
|
||||||
showConversationsOverview();
|
showConversationsOverview();
|
||||||
mPendingImageUri = null;
|
mPendingImageUri = null;
|
||||||
|
mPendingFileUri = null;
|
||||||
setSelectedConversation(conversationList.get(0));
|
setSelectedConversation(conversationList.get(0));
|
||||||
this.mConversationFragment.reInit(getSelectedConversation());
|
this.mConversationFragment.reInit(getSelectedConversation());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mPendingImageUri != null) {
|
if (mPendingImageUri != null) {
|
||||||
attachImageToConversation(getSelectedConversation(),
|
attachImageToConversation(getSelectedConversation(),mPendingImageUri);
|
||||||
mPendingImageUri);
|
|
||||||
mPendingImageUri = null;
|
mPendingImageUri = null;
|
||||||
|
} else if (mPendingFileUri != null) {
|
||||||
|
attachFileToConversation(getSelectedConversation(),mPendingFileUri);
|
||||||
|
mPendingFileUri = null;
|
||||||
}
|
}
|
||||||
ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
|
ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
|
||||||
setIntent(new Intent());
|
setIntent(new Intent());
|
||||||
|
@ -726,13 +736,20 @@ public class ConversationActivity extends XmppActivity implements
|
||||||
selectedFragment.hideSnackbar();
|
selectedFragment.hideSnackbar();
|
||||||
selectedFragment.updateMessages();
|
selectedFragment.updateMessages();
|
||||||
}
|
}
|
||||||
} else if (requestCode == REQUEST_ATTACH_FILE_DIALOG) {
|
} else if (requestCode == REQUEST_ATTACH_IMAGE_DIALOG) {
|
||||||
mPendingImageUri = data.getData();
|
mPendingImageUri = data.getData();
|
||||||
if (xmppConnectionServiceBound) {
|
if (xmppConnectionServiceBound) {
|
||||||
attachImageToConversation(getSelectedConversation(),
|
attachImageToConversation(getSelectedConversation(),
|
||||||
mPendingImageUri);
|
mPendingImageUri);
|
||||||
mPendingImageUri = null;
|
mPendingImageUri = null;
|
||||||
}
|
}
|
||||||
|
} else if (requestCode == REQUEST_ATTACH_FILE_DIALOG) {
|
||||||
|
mPendingFileUri = data.getData();
|
||||||
|
if (xmppConnectionServiceBound) {
|
||||||
|
attachFileToConversation(getSelectedConversation(),
|
||||||
|
mPendingFileUri);
|
||||||
|
mPendingFileUri = null;
|
||||||
|
}
|
||||||
} else if (requestCode == REQUEST_SEND_PGP_IMAGE) {
|
} else if (requestCode == REQUEST_SEND_PGP_IMAGE) {
|
||||||
|
|
||||||
} else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_IMAGE) {
|
} else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_IMAGE) {
|
||||||
|
@ -754,9 +771,6 @@ public class ConversationActivity extends XmppActivity implements
|
||||||
Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
|
Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
|
||||||
intent.setData(mPendingImageUri);
|
intent.setData(mPendingImageUri);
|
||||||
sendBroadcast(intent);
|
sendBroadcast(intent);
|
||||||
} else if (requestCode == REQUEST_RECORD_AUDIO) {
|
|
||||||
attachAudioToConversation(getSelectedConversation(),
|
|
||||||
data.getData());
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (requestCode == REQUEST_IMAGE_CAPTURE) {
|
if (requestCode == REQUEST_IMAGE_CAPTURE) {
|
||||||
|
@ -765,21 +779,40 @@ public class ConversationActivity extends XmppActivity implements
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void attachAudioToConversation(Conversation conversation, Uri uri) {
|
private void attachFileToConversation(Conversation conversation, Uri uri) {
|
||||||
|
prepareFileToast = Toast.makeText(getApplicationContext(),
|
||||||
|
getText(R.string.preparing_file), Toast.LENGTH_LONG);
|
||||||
|
prepareFileToast.show();
|
||||||
|
xmppConnectionService.attachFileToConversation(conversation,uri, new UiCallback<Message>() {
|
||||||
|
@Override
|
||||||
|
public void success(Message message) {
|
||||||
|
hidePrepareFileToast();
|
||||||
|
xmppConnectionService.sendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error(int errorCode, Message message) {
|
||||||
|
displayErrorDialog(errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void userInputRequried(PendingIntent pi, Message message) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void attachImageToConversation(Conversation conversation, Uri uri) {
|
private void attachImageToConversation(Conversation conversation, Uri uri) {
|
||||||
prepareImageToast = Toast.makeText(getApplicationContext(),
|
prepareFileToast = Toast.makeText(getApplicationContext(),
|
||||||
getText(R.string.preparing_image), Toast.LENGTH_LONG);
|
getText(R.string.preparing_image), Toast.LENGTH_LONG);
|
||||||
prepareImageToast.show();
|
prepareFileToast.show();
|
||||||
xmppConnectionService.attachImageToConversation(conversation, uri,
|
xmppConnectionService.attachImageToConversation(conversation, uri,
|
||||||
new UiCallback<Message>() {
|
new UiCallback<Message>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void userInputRequried(PendingIntent pi,
|
public void userInputRequried(PendingIntent pi,
|
||||||
Message object) {
|
Message object) {
|
||||||
hidePrepareImageToast();
|
hidePrepareFileToast();
|
||||||
ConversationActivity.this.runIntent(pi,
|
ConversationActivity.this.runIntent(pi,
|
||||||
ConversationActivity.REQUEST_SEND_PGP_IMAGE);
|
ConversationActivity.REQUEST_SEND_PGP_IMAGE);
|
||||||
}
|
}
|
||||||
|
@ -791,19 +824,19 @@ public class ConversationActivity extends XmppActivity implements
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void error(int error, Message message) {
|
public void error(int error, Message message) {
|
||||||
hidePrepareImageToast();
|
hidePrepareFileToast();
|
||||||
displayErrorDialog(error);
|
displayErrorDialog(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void hidePrepareImageToast() {
|
private void hidePrepareFileToast() {
|
||||||
if (prepareImageToast != null) {
|
if (prepareFileToast != null) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
prepareImageToast.cancel();
|
prepareFileToast.cancel();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,9 @@ import eu.siacs.conversations.crypto.PgpEngine;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.entities.Contact;
|
import eu.siacs.conversations.entities.Contact;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
|
import eu.siacs.conversations.entities.Downloadable;
|
||||||
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
|
import eu.siacs.conversations.entities.DownloadablePlaceholder;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.entities.MucOptions;
|
import eu.siacs.conversations.entities.MucOptions;
|
||||||
import eu.siacs.conversations.entities.Presences;
|
import eu.siacs.conversations.entities.Presences;
|
||||||
|
@ -354,6 +357,7 @@ public class ConversationFragment extends Fragment {
|
||||||
MenuItem sendAgain = menu.findItem(R.id.send_again);
|
MenuItem sendAgain = menu.findItem(R.id.send_again);
|
||||||
MenuItem copyUrl = menu.findItem(R.id.copy_url);
|
MenuItem copyUrl = menu.findItem(R.id.copy_url);
|
||||||
MenuItem downloadImage = menu.findItem(R.id.download_image);
|
MenuItem downloadImage = menu.findItem(R.id.download_image);
|
||||||
|
MenuItem cancelTransmission = menu.findItem(R.id.cancel_transmission);
|
||||||
if (this.selectedMessage.getType() != Message.TYPE_TEXT
|
if (this.selectedMessage.getType() != Message.TYPE_TEXT
|
||||||
|| this.selectedMessage.getDownloadable() != null) {
|
|| this.selectedMessage.getDownloadable() != null) {
|
||||||
copyText.setVisible(false);
|
copyText.setVisible(false);
|
||||||
|
@ -370,12 +374,15 @@ public class ConversationFragment extends Fragment {
|
||||||
|| this.selectedMessage.getImageParams().url == null) {
|
|| this.selectedMessage.getImageParams().url == null) {
|
||||||
copyUrl.setVisible(false);
|
copyUrl.setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.selectedMessage.getType() != Message.TYPE_TEXT
|
if (this.selectedMessage.getType() != Message.TYPE_TEXT
|
||||||
|| this.selectedMessage.getDownloadable() != null
|
|| this.selectedMessage.getDownloadable() != null
|
||||||
|| !this.selectedMessage.bodyContainsDownloadable()) {
|
|| !this.selectedMessage.bodyContainsDownloadable()) {
|
||||||
downloadImage.setVisible(false);
|
downloadImage.setVisible(false);
|
||||||
}
|
}
|
||||||
|
if (this.selectedMessage.getDownloadable() == null
|
||||||
|
|| this.selectedMessage.getDownloadable() instanceof DownloadablePlaceholder) {
|
||||||
|
cancelTransmission.setVisible(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,6 +404,9 @@ public class ConversationFragment extends Fragment {
|
||||||
case R.id.download_image:
|
case R.id.download_image:
|
||||||
downloadImage(selectedMessage);
|
downloadImage(selectedMessage);
|
||||||
return true;
|
return true;
|
||||||
|
case R.id.cancel_transmission:
|
||||||
|
cancelTransmission(selectedMessage);
|
||||||
|
return true;
|
||||||
default:
|
default:
|
||||||
return super.onContextItemSelected(item);
|
return super.onContextItemSelected(item);
|
||||||
}
|
}
|
||||||
|
@ -423,6 +433,14 @@ public class ConversationFragment extends Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resendMessage(Message message) {
|
private void resendMessage(Message message) {
|
||||||
|
if (message.getType() == Message.TYPE_FILE || message.getType() == Message.TYPE_IMAGE) {
|
||||||
|
DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
|
||||||
|
if (!file.exists()) {
|
||||||
|
Toast.makeText(activity,R.string.file_deleted,Toast.LENGTH_SHORT).show();
|
||||||
|
message.setDownloadable(new DownloadablePlaceholder(Downloadable.STATUS_DELETED));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
activity.xmppConnectionService.resendFailedMessages(message);
|
activity.xmppConnectionService.resendFailedMessages(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -439,6 +457,13 @@ public class ConversationFragment extends Fragment {
|
||||||
.createNewConnection(message);
|
.createNewConnection(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void cancelTransmission(Message message) {
|
||||||
|
Downloadable downloadable = message.getDownloadable();
|
||||||
|
if (downloadable!=null) {
|
||||||
|
downloadable.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void privateMessageWith(final Jid counterpart) {
|
protected void privateMessageWith(final Jid counterpart) {
|
||||||
this.mEditMessage.setText("");
|
this.mEditMessage.setText("");
|
||||||
this.conversation.setNextCounterpart(counterpart);
|
this.conversation.setNextCounterpart(counterpart);
|
||||||
|
|
|
@ -6,6 +6,7 @@ import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
import eu.siacs.conversations.entities.Downloadable;
|
import eu.siacs.conversations.entities.Downloadable;
|
||||||
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.ui.ConversationActivity;
|
import eu.siacs.conversations.ui.ConversationActivity;
|
||||||
import eu.siacs.conversations.ui.XmppActivity;
|
import eu.siacs.conversations.ui.XmppActivity;
|
||||||
|
@ -75,7 +76,7 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
|
||||||
convName.setTypeface(null, Typeface.NORMAL);
|
convName.setTypeface(null, Typeface.NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.getType() == Message.TYPE_IMAGE
|
if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE
|
||||||
|| message.getDownloadable() != null) {
|
|| message.getDownloadable() != null) {
|
||||||
Downloadable d = message.getDownloadable();
|
Downloadable d = message.getDownloadable();
|
||||||
if (conversation.isRead()) {
|
if (conversation.isRead()) {
|
||||||
|
@ -89,13 +90,35 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
|
||||||
if (d.getStatus() == Downloadable.STATUS_CHECKING) {
|
if (d.getStatus() == Downloadable.STATUS_CHECKING) {
|
||||||
mLastMessage.setText(R.string.checking_image);
|
mLastMessage.setText(R.string.checking_image);
|
||||||
} else if (d.getStatus() == Downloadable.STATUS_DOWNLOADING) {
|
} else if (d.getStatus() == Downloadable.STATUS_DOWNLOADING) {
|
||||||
mLastMessage.setText(R.string.receiving_image);
|
if (message.getType() == Message.TYPE_FILE) {
|
||||||
|
mLastMessage.setText(getContext().getString(R.string.receiving_file,d.getMimeType(), d.getProgress()));
|
||||||
|
} else {
|
||||||
|
mLastMessage.setText(getContext().getString(R.string.receiving_image, d.getProgress()));
|
||||||
|
}
|
||||||
} else if (d.getStatus() == Downloadable.STATUS_OFFER) {
|
} else if (d.getStatus() == Downloadable.STATUS_OFFER) {
|
||||||
mLastMessage.setText(R.string.image_offered_for_download);
|
if (message.getType() == Message.TYPE_FILE) {
|
||||||
|
mLastMessage.setText(R.string.file_offered_for_download);
|
||||||
|
} else {
|
||||||
|
mLastMessage.setText(R.string.image_offered_for_download);
|
||||||
|
}
|
||||||
} else if (d.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) {
|
} else if (d.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) {
|
||||||
mLastMessage.setText(R.string.image_offered_for_download);
|
mLastMessage.setText(R.string.image_offered_for_download);
|
||||||
} else if (d.getStatus() == Downloadable.STATUS_DELETED) {
|
} else if (d.getStatus() == Downloadable.STATUS_DELETED) {
|
||||||
mLastMessage.setText(R.string.image_file_deleted);
|
if (message.getType() == Message.TYPE_FILE) {
|
||||||
|
mLastMessage.setText(R.string.file_deleted);
|
||||||
|
} else {
|
||||||
|
mLastMessage.setText(R.string.image_file_deleted);
|
||||||
|
}
|
||||||
|
} else if (d.getStatus() == Downloadable.STATUS_FAILED) {
|
||||||
|
if (message.getType() == Message.TYPE_FILE) {
|
||||||
|
mLastMessage.setText(R.string.file_transmission_failed);
|
||||||
|
} else {
|
||||||
|
mLastMessage.setText(R.string.image_transmission_failed);
|
||||||
|
}
|
||||||
|
} else if (message.getImageParams().width > 0) {
|
||||||
|
mLastMessage.setVisibility(View.GONE);
|
||||||
|
imagePreview.setVisibility(View.VISIBLE);
|
||||||
|
activity.loadBitmap(message, imagePreview);
|
||||||
} else {
|
} else {
|
||||||
mLastMessage.setText("");
|
mLastMessage.setText("");
|
||||||
}
|
}
|
||||||
|
@ -103,6 +126,11 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
|
||||||
imagePreview.setVisibility(View.GONE);
|
imagePreview.setVisibility(View.GONE);
|
||||||
mLastMessage.setVisibility(View.VISIBLE);
|
mLastMessage.setVisibility(View.VISIBLE);
|
||||||
mLastMessage.setText(R.string.encrypted_message_received);
|
mLastMessage.setText(R.string.encrypted_message_received);
|
||||||
|
} else if (message.getType() == Message.TYPE_FILE && message.getImageParams().width <= 0) {
|
||||||
|
DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
|
||||||
|
mLastMessage.setVisibility(View.VISIBLE);
|
||||||
|
imagePreview.setVisibility(View.GONE);
|
||||||
|
mLastMessage.setText(getContext().getString(R.string.file,file.getMimeType()));
|
||||||
} else {
|
} else {
|
||||||
mLastMessage.setVisibility(View.GONE);
|
mLastMessage.setVisibility(View.GONE);
|
||||||
imagePreview.setVisibility(View.VISIBLE);
|
imagePreview.setVisibility(View.VISIBLE);
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
package eu.siacs.conversations.ui.adapter;
|
package eu.siacs.conversations.ui.adapter;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
|
import android.net.Uri;
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.style.ForegroundColorSpan;
|
import android.text.style.ForegroundColorSpan;
|
||||||
import android.text.style.StyleSpan;
|
import android.text.style.StyleSpan;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.view.View.OnLongClickListener;
|
import android.view.View.OnLongClickListener;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.webkit.MimeTypeMap;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
@ -18,6 +23,9 @@ import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
|
@ -25,6 +33,7 @@ import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.entities.Contact;
|
import eu.siacs.conversations.entities.Contact;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
import eu.siacs.conversations.entities.Downloadable;
|
import eu.siacs.conversations.entities.Downloadable;
|
||||||
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.entities.Message.ImageParams;
|
import eu.siacs.conversations.entities.Message.ImageParams;
|
||||||
import eu.siacs.conversations.ui.ConversationActivity;
|
import eu.siacs.conversations.ui.ConversationActivity;
|
||||||
|
@ -96,10 +105,11 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
}
|
}
|
||||||
boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
|
boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
|
||||||
&& message.getMergedStatus() <= Message.STATUS_RECEIVED;
|
&& message.getMergedStatus() <= Message.STATUS_RECEIVED;
|
||||||
if (message.getType() == Message.TYPE_IMAGE
|
if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.getDownloadable() != null) {
|
||||||
|| message.getDownloadable() != null) {
|
|
||||||
ImageParams params = message.getImageParams();
|
ImageParams params = message.getImageParams();
|
||||||
if (params.size != 0) {
|
if (params.size > (1.5 * 1024 * 1024)) {
|
||||||
|
filesize = params.size / (1024 * 1024)+ " MB";
|
||||||
|
} else if (params.size > 0) {
|
||||||
filesize = params.size / 1024 + " KB";
|
filesize = params.size / 1024 + " KB";
|
||||||
}
|
}
|
||||||
if (message.getDownloadable() != null && message.getDownloadable().getStatus() == Downloadable.STATUS_FAILED) {
|
if (message.getDownloadable() != null && message.getDownloadable().getStatus() == Downloadable.STATUS_FAILED) {
|
||||||
|
@ -111,7 +121,12 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
info = getContext().getString(R.string.waiting);
|
info = getContext().getString(R.string.waiting);
|
||||||
break;
|
break;
|
||||||
case Message.STATUS_UNSEND:
|
case Message.STATUS_UNSEND:
|
||||||
info = getContext().getString(R.string.sending);
|
Downloadable d = message.getDownloadable();
|
||||||
|
if (d!=null) {
|
||||||
|
info = getContext().getString(R.string.sending_file,d.getProgress());
|
||||||
|
} else {
|
||||||
|
info = getContext().getString(R.string.sending);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Message.STATUS_OFFERED:
|
case Message.STATUS_OFFERED:
|
||||||
info = getContext().getString(R.string.offering);
|
info = getContext().getString(R.string.offering);
|
||||||
|
@ -181,13 +196,13 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void displayInfoMessage(ViewHolder viewHolder, int r) {
|
private void displayInfoMessage(ViewHolder viewHolder, String text) {
|
||||||
if (viewHolder.download_button != null) {
|
if (viewHolder.download_button != null) {
|
||||||
viewHolder.download_button.setVisibility(View.GONE);
|
viewHolder.download_button.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
viewHolder.image.setVisibility(View.GONE);
|
viewHolder.image.setVisibility(View.GONE);
|
||||||
viewHolder.messageBody.setVisibility(View.VISIBLE);
|
viewHolder.messageBody.setVisibility(View.VISIBLE);
|
||||||
viewHolder.messageBody.setText(getContext().getString(r));
|
viewHolder.messageBody.setText(text);
|
||||||
viewHolder.messageBody.setTextColor(activity.getSecondaryTextColor());
|
viewHolder.messageBody.setTextColor(activity.getSecondaryTextColor());
|
||||||
viewHolder.messageBody.setTypeface(null, Typeface.ITALIC);
|
viewHolder.messageBody.setTypeface(null, Typeface.ITALIC);
|
||||||
viewHolder.messageBody.setTextIsSelectable(false);
|
viewHolder.messageBody.setTextIsSelectable(false);
|
||||||
|
@ -252,11 +267,11 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void displayDownloadableMessage(ViewHolder viewHolder,
|
private void displayDownloadableMessage(ViewHolder viewHolder,
|
||||||
final Message message, int resid) {
|
final Message message, String text) {
|
||||||
viewHolder.image.setVisibility(View.GONE);
|
viewHolder.image.setVisibility(View.GONE);
|
||||||
viewHolder.messageBody.setVisibility(View.GONE);
|
viewHolder.messageBody.setVisibility(View.GONE);
|
||||||
viewHolder.download_button.setVisibility(View.VISIBLE);
|
viewHolder.download_button.setVisibility(View.VISIBLE);
|
||||||
viewHolder.download_button.setText(resid);
|
viewHolder.download_button.setText(text);
|
||||||
viewHolder.download_button.setOnClickListener(new OnClickListener() {
|
viewHolder.download_button.setOnClickListener(new OnClickListener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -267,6 +282,22 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
viewHolder.download_button.setOnLongClickListener(openContextMenu);
|
viewHolder.download_button.setOnLongClickListener(openContextMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void displayOpenableMessage(ViewHolder viewHolder,final Message message) {
|
||||||
|
final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
|
||||||
|
viewHolder.image.setVisibility(View.GONE);
|
||||||
|
viewHolder.messageBody.setVisibility(View.GONE);
|
||||||
|
viewHolder.download_button.setVisibility(View.VISIBLE);
|
||||||
|
viewHolder.download_button.setText(activity.getString(R.string.open_file,file.getMimeType()));
|
||||||
|
viewHolder.download_button.setOnClickListener(new OnClickListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
openDonwloadable(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
viewHolder.download_button.setOnLongClickListener(openContextMenu);
|
||||||
|
}
|
||||||
|
|
||||||
private void displayImageMessage(ViewHolder viewHolder,
|
private void displayImageMessage(ViewHolder viewHolder,
|
||||||
final Message message) {
|
final Message message) {
|
||||||
if (viewHolder.download_button != null) {
|
if (viewHolder.download_button != null) {
|
||||||
|
@ -455,58 +486,66 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.getType() == Message.TYPE_IMAGE
|
if (item.getDownloadable() != null && item.getDownloadable().getStatus() != Downloadable.STATUS_UPLOADING) {
|
||||||
|| item.getDownloadable() != null) {
|
|
||||||
Downloadable d = item.getDownloadable();
|
Downloadable d = item.getDownloadable();
|
||||||
if (d != null && d.getStatus() == Downloadable.STATUS_DOWNLOADING) {
|
if (d.getStatus() == Downloadable.STATUS_DOWNLOADING) {
|
||||||
displayInfoMessage(viewHolder, R.string.receiving_image);
|
if (item.getType() == Message.TYPE_FILE) {
|
||||||
} else if (d != null
|
displayInfoMessage(viewHolder,activity.getString(R.string.receiving_file,d.getMimeType(),d.getProgress()));
|
||||||
&& d.getStatus() == Downloadable.STATUS_CHECKING) {
|
|
||||||
displayInfoMessage(viewHolder, R.string.checking_image);
|
|
||||||
} else if (d != null
|
|
||||||
&& d.getStatus() == Downloadable.STATUS_DELETED) {
|
|
||||||
displayInfoMessage(viewHolder, R.string.image_file_deleted);
|
|
||||||
} else if (d != null && d.getStatus() == Downloadable.STATUS_OFFER) {
|
|
||||||
displayDownloadableMessage(viewHolder, item,
|
|
||||||
R.string.download_image);
|
|
||||||
} else if (d != null
|
|
||||||
&& d.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) {
|
|
||||||
displayDownloadableMessage(viewHolder, item,
|
|
||||||
R.string.check_image_filesize);
|
|
||||||
} else if (d != null && d.getStatus() == Downloadable.STATUS_FAILED) {
|
|
||||||
displayInfoMessage(viewHolder, R.string.image_transmission_failed);
|
|
||||||
} else if ((item.getEncryption() == Message.ENCRYPTION_DECRYPTED)
|
|
||||||
|| (item.getEncryption() == Message.ENCRYPTION_NONE)
|
|
||||||
|| (item.getEncryption() == Message.ENCRYPTION_OTR)) {
|
|
||||||
displayImageMessage(viewHolder, item);
|
|
||||||
} else if (item.getEncryption() == Message.ENCRYPTION_PGP) {
|
|
||||||
displayInfoMessage(viewHolder, R.string.encrypted_message);
|
|
||||||
} else {
|
|
||||||
displayDecryptionFailed(viewHolder);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (item.getEncryption() == Message.ENCRYPTION_PGP) {
|
|
||||||
if (activity.hasPgp()) {
|
|
||||||
displayInfoMessage(viewHolder, R.string.encrypted_message);
|
|
||||||
} else {
|
} else {
|
||||||
displayInfoMessage(viewHolder,
|
displayInfoMessage(viewHolder,activity.getString(R.string.receiving_image,d.getProgress()));
|
||||||
R.string.install_openkeychain);
|
}
|
||||||
if (viewHolder != null) {
|
} else if (d.getStatus() == Downloadable.STATUS_CHECKING) {
|
||||||
viewHolder.message_box
|
displayInfoMessage(viewHolder,activity.getString(R.string.checking_image));
|
||||||
.setOnClickListener(new OnClickListener() {
|
} else if (d.getStatus() == Downloadable.STATUS_DELETED) {
|
||||||
|
if (item.getType() == Message.TYPE_FILE) {
|
||||||
@Override
|
displayInfoMessage(viewHolder, activity.getString(R.string.file_deleted));
|
||||||
public void onClick(View v) {
|
} else {
|
||||||
activity.showInstallPgpDialog();
|
displayInfoMessage(viewHolder, activity.getString(R.string.image_file_deleted));
|
||||||
}
|
}
|
||||||
});
|
} else if (d.getStatus() == Downloadable.STATUS_OFFER) {
|
||||||
}
|
if (item.getType() == Message.TYPE_FILE) {
|
||||||
|
displayDownloadableMessage(viewHolder,item,activity.getString(R.string.download_file,d.getMimeType()));
|
||||||
|
} else {
|
||||||
|
displayDownloadableMessage(viewHolder, item,activity.getString(R.string.download_image));
|
||||||
|
}
|
||||||
|
} else if (d.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) {
|
||||||
|
displayDownloadableMessage(viewHolder, item,activity.getString(R.string.check_image_filesize));
|
||||||
|
} else if (d.getStatus() == Downloadable.STATUS_FAILED) {
|
||||||
|
if (item.getType() == Message.TYPE_FILE) {
|
||||||
|
displayInfoMessage(viewHolder, activity.getString(R.string.file_transmission_failed));
|
||||||
|
} else {
|
||||||
|
displayInfoMessage(viewHolder, activity.getString(R.string.image_transmission_failed));
|
||||||
}
|
}
|
||||||
} else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
|
|
||||||
displayDecryptionFailed(viewHolder);
|
|
||||||
} else {
|
|
||||||
displayTextMessage(viewHolder, item);
|
|
||||||
}
|
}
|
||||||
|
} else if (item.getType() == Message.TYPE_IMAGE && item.getEncryption() != Message.ENCRYPTION_PGP && item.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
|
||||||
|
displayImageMessage(viewHolder, item);
|
||||||
|
} else if (item.getType() == Message.TYPE_FILE && item.getEncryption() != Message.ENCRYPTION_PGP && item.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
|
||||||
|
if (item.getImageParams().width > 0) {
|
||||||
|
displayImageMessage(viewHolder,item);
|
||||||
|
} else {
|
||||||
|
displayOpenableMessage(viewHolder, item);
|
||||||
|
}
|
||||||
|
} else if (item.getEncryption() == Message.ENCRYPTION_PGP) {
|
||||||
|
if (activity.hasPgp()) {
|
||||||
|
displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message));
|
||||||
|
} else {
|
||||||
|
displayInfoMessage(viewHolder,
|
||||||
|
activity.getString(R.string.install_openkeychain));
|
||||||
|
if (viewHolder != null) {
|
||||||
|
viewHolder.message_box
|
||||||
|
.setOnClickListener(new OnClickListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
activity.showInstallPgpDialog();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
|
||||||
|
displayDecryptionFailed(viewHolder);
|
||||||
|
} else {
|
||||||
|
displayTextMessage(viewHolder, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
displayStatus(viewHolder, item);
|
displayStatus(viewHolder, item);
|
||||||
|
@ -524,6 +563,22 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void openDonwloadable(DownloadableFile file) {
|
||||||
|
if (!file.exists()) {
|
||||||
|
Toast.makeText(activity,R.string.file_deleted,Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Intent openIntent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
openIntent.setDataAndType(Uri.fromFile(file), file.getMimeType());
|
||||||
|
PackageManager manager = activity.getPackageManager();
|
||||||
|
List<ResolveInfo> infos = manager.queryIntentActivities(openIntent, 0);
|
||||||
|
if (infos.size() > 0) {
|
||||||
|
getContext().startActivity(openIntent);
|
||||||
|
} else {
|
||||||
|
Toast.makeText(activity,R.string.no_application_found_to_open_file,Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public interface OnContactPictureClicked {
|
public interface OnContactPictureClicked {
|
||||||
public void onContactPictureClicked(Message message);
|
public void onContactPictureClicked(Message message);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package eu.siacs.conversations.xmpp.jingle;
|
package eu.siacs.conversations.xmpp.jingle;
|
||||||
|
|
||||||
|
import java.net.URLConnection;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -9,14 +10,15 @@ import java.util.Map.Entry;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.BitmapFactory;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.SystemClock;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
import eu.siacs.conversations.entities.Downloadable;
|
import eu.siacs.conversations.entities.Downloadable;
|
||||||
import eu.siacs.conversations.entities.DownloadableFile;
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
|
import eu.siacs.conversations.entities.DownloadablePlaceholder;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
|
@ -29,9 +31,6 @@ import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
||||||
|
|
||||||
public class JingleConnection implements Downloadable {
|
public class JingleConnection implements Downloadable {
|
||||||
|
|
||||||
private final String[] extensions = { "webp", "jpeg", "jpg", "png" };
|
|
||||||
private final String[] cryptoExtensions = { "pgp", "gpg", "otr" };
|
|
||||||
|
|
||||||
private JingleConnectionManager mJingleConnectionManager;
|
private JingleConnectionManager mJingleConnectionManager;
|
||||||
private XmppConnectionService mXmppConnectionService;
|
private XmppConnectionService mXmppConnectionService;
|
||||||
|
|
||||||
|
@ -46,7 +45,7 @@ public class JingleConnection implements Downloadable {
|
||||||
private int ibbBlockSize = 4096;
|
private int ibbBlockSize = 4096;
|
||||||
|
|
||||||
private int mJingleStatus = -1;
|
private int mJingleStatus = -1;
|
||||||
private int mStatus = -1;
|
private int mStatus = Downloadable.STATUS_UNKNOWN;
|
||||||
private Message message;
|
private Message message;
|
||||||
private String sessionId;
|
private String sessionId;
|
||||||
private Account account;
|
private Account account;
|
||||||
|
@ -62,6 +61,9 @@ public class JingleConnection implements Downloadable {
|
||||||
private String contentName;
|
private String contentName;
|
||||||
private String contentCreator;
|
private String contentCreator;
|
||||||
|
|
||||||
|
private int mProgress = 0;
|
||||||
|
private long mLastGuiRefresh = 0;
|
||||||
|
|
||||||
private boolean receivedCandidate = false;
|
private boolean receivedCandidate = false;
|
||||||
private boolean sentCandidate = false;
|
private boolean sentCandidate = false;
|
||||||
|
|
||||||
|
@ -74,7 +76,7 @@ public class JingleConnection implements Downloadable {
|
||||||
@Override
|
@Override
|
||||||
public void onIqPacketReceived(Account account, IqPacket packet) {
|
public void onIqPacketReceived(Account account, IqPacket packet) {
|
||||||
if (packet.getType() == IqPacket.TYPE_ERROR) {
|
if (packet.getType() == IqPacket.TYPE_ERROR) {
|
||||||
cancel();
|
fail();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -90,16 +92,14 @@ public class JingleConnection implements Downloadable {
|
||||||
JingleConnection.this.mXmppConnectionService
|
JingleConnection.this.mXmppConnectionService
|
||||||
.getNotificationService().push(message);
|
.getNotificationService().push(message);
|
||||||
}
|
}
|
||||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
mXmppConnectionService.getFileBackend().updateFileParams(message);
|
||||||
options.inJustDecodeBounds = true;
|
|
||||||
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
|
|
||||||
int imageHeight = options.outHeight;
|
|
||||||
int imageWidth = options.outWidth;
|
|
||||||
message.setBody(Long.toString(file.getSize()) + '|'
|
|
||||||
+ imageWidth + '|' + imageHeight);
|
|
||||||
mXmppConnectionService.databaseBackend.createMessage(message);
|
mXmppConnectionService.databaseBackend.createMessage(message);
|
||||||
mXmppConnectionService.markMessage(message,
|
mXmppConnectionService.markMessage(message,
|
||||||
Message.STATUS_RECEIVED);
|
Message.STATUS_RECEIVED);
|
||||||
|
} else {
|
||||||
|
if (message.getEncryption() == Message.ENCRYPTION_PGP || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Log.d(Config.LOGTAG,
|
Log.d(Config.LOGTAG,
|
||||||
"sucessfully transmitted file:" + file.getAbsolutePath());
|
"sucessfully transmitted file:" + file.getAbsolutePath());
|
||||||
|
@ -114,7 +114,7 @@ public class JingleConnection implements Downloadable {
|
||||||
@Override
|
@Override
|
||||||
public void onFileTransferAborted() {
|
public void onFileTransferAborted() {
|
||||||
JingleConnection.this.sendCancel();
|
JingleConnection.this.sendCancel();
|
||||||
JingleConnection.this.cancel();
|
JingleConnection.this.fail();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -161,14 +161,14 @@ public class JingleConnection implements Downloadable {
|
||||||
Reason reason = packet.getReason();
|
Reason reason = packet.getReason();
|
||||||
if (reason != null) {
|
if (reason != null) {
|
||||||
if (reason.hasChild("cancel")) {
|
if (reason.hasChild("cancel")) {
|
||||||
this.cancel();
|
this.fail();
|
||||||
} else if (reason.hasChild("success")) {
|
} else if (reason.hasChild("success")) {
|
||||||
this.receiveSuccess();
|
this.receiveSuccess();
|
||||||
} else {
|
} else {
|
||||||
this.cancel();
|
this.fail();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.cancel();
|
this.fail();
|
||||||
}
|
}
|
||||||
} else if (packet.isAction("session-accept")) {
|
} else if (packet.isAction("session-accept")) {
|
||||||
returnResult = receiveAccept(packet);
|
returnResult = receiveAccept(packet);
|
||||||
|
@ -203,6 +203,8 @@ public class JingleConnection implements Downloadable {
|
||||||
this.contentCreator = "initiator";
|
this.contentCreator = "initiator";
|
||||||
this.contentName = this.mJingleConnectionManager.nextRandomId();
|
this.contentName = this.mJingleConnectionManager.nextRandomId();
|
||||||
this.message = message;
|
this.message = message;
|
||||||
|
this.message.setDownloadable(this);
|
||||||
|
this.mStatus = Downloadable.STATUS_UPLOADING;
|
||||||
this.account = message.getConversation().getAccount();
|
this.account = message.getConversation().getAccount();
|
||||||
this.initiator = this.account.getJid();
|
this.initiator = this.account.getJid();
|
||||||
this.responder = this.message.getCounterpart();
|
this.responder = this.message.getCounterpart();
|
||||||
|
@ -258,7 +260,6 @@ public class JingleConnection implements Downloadable {
|
||||||
packet.getFrom().toBareJid(), false);
|
packet.getFrom().toBareJid(), false);
|
||||||
this.message = new Message(conversation, "", Message.ENCRYPTION_NONE);
|
this.message = new Message(conversation, "", Message.ENCRYPTION_NONE);
|
||||||
this.message.setStatus(Message.STATUS_RECEIVED);
|
this.message.setStatus(Message.STATUS_RECEIVED);
|
||||||
this.message.setType(Message.TYPE_IMAGE);
|
|
||||||
this.mStatus = Downloadable.STATUS_OFFER;
|
this.mStatus = Downloadable.STATUS_OFFER;
|
||||||
this.message.setDownloadable(this);
|
this.message.setDownloadable(this);
|
||||||
final Jid from = packet.getFrom();
|
final Jid from = packet.getFrom();
|
||||||
|
@ -278,75 +279,83 @@ public class JingleConnection implements Downloadable {
|
||||||
Element fileSize = fileOffer.findChild("size");
|
Element fileSize = fileOffer.findChild("size");
|
||||||
Element fileNameElement = fileOffer.findChild("name");
|
Element fileNameElement = fileOffer.findChild("name");
|
||||||
if (fileNameElement != null) {
|
if (fileNameElement != null) {
|
||||||
boolean supportedFile = false;
|
|
||||||
String[] filename = fileNameElement.getContent()
|
String[] filename = fileNameElement.getContent()
|
||||||
.toLowerCase(Locale.US).split("\\.");
|
.toLowerCase(Locale.US).split("\\.");
|
||||||
if (Arrays.asList(this.extensions).contains(
|
if (Arrays.asList(VALID_IMAGE_EXTENSIONS).contains(
|
||||||
filename[filename.length - 1])) {
|
filename[filename.length - 1])) {
|
||||||
supportedFile = true;
|
message.setType(Message.TYPE_IMAGE);
|
||||||
} else if (Arrays.asList(this.cryptoExtensions).contains(
|
} else if (Arrays.asList(VALID_CRYPTO_EXTENSIONS).contains(
|
||||||
filename[filename.length - 1])) {
|
filename[filename.length - 1])) {
|
||||||
if (filename.length == 3) {
|
if (filename.length == 3) {
|
||||||
if (Arrays.asList(this.extensions).contains(
|
if (Arrays.asList(VALID_IMAGE_EXTENSIONS).contains(
|
||||||
filename[filename.length - 2])) {
|
filename[filename.length - 2])) {
|
||||||
supportedFile = true;
|
message.setType(Message.TYPE_IMAGE);
|
||||||
if (filename[filename.length - 1].equals("otr")) {
|
|
||||||
Log.d(Config.LOGTAG, "receiving otr file");
|
|
||||||
this.message
|
|
||||||
.setEncryption(Message.ENCRYPTION_OTR);
|
|
||||||
} else {
|
|
||||||
this.message
|
|
||||||
.setEncryption(Message.ENCRYPTION_PGP);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (supportedFile) {
|
|
||||||
long size = Long.parseLong(fileSize.getContent());
|
|
||||||
message.setBody(Long.toString(size));
|
|
||||||
conversation.add(message);
|
|
||||||
mXmppConnectionService.updateConversationUi();
|
|
||||||
if (size <= this.mJingleConnectionManager
|
|
||||||
.getAutoAcceptFileSize()) {
|
|
||||||
Log.d(Config.LOGTAG, "auto accepting file from "
|
|
||||||
+ packet.getFrom());
|
|
||||||
this.acceptedAutomatically = true;
|
|
||||||
this.sendAccept();
|
|
||||||
} else {
|
|
||||||
message.markUnread();
|
|
||||||
Log.d(Config.LOGTAG,
|
|
||||||
"not auto accepting new file offer with size: "
|
|
||||||
+ size
|
|
||||||
+ " allowed size:"
|
|
||||||
+ this.mJingleConnectionManager
|
|
||||||
.getAutoAcceptFileSize());
|
|
||||||
this.mXmppConnectionService.getNotificationService()
|
|
||||||
.push(message);
|
|
||||||
}
|
|
||||||
this.file = this.mXmppConnectionService.getFileBackend()
|
|
||||||
.getFile(message, false);
|
|
||||||
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
|
||||||
byte[] key = conversation.getSymmetricKey();
|
|
||||||
if (key == null) {
|
|
||||||
this.sendCancel();
|
|
||||||
this.cancel();
|
|
||||||
return;
|
|
||||||
} else {
|
} else {
|
||||||
this.file.setKey(key);
|
message.setType(Message.TYPE_FILE);
|
||||||
|
}
|
||||||
|
if (filename[filename.length - 1].equals("otr")) {
|
||||||
|
message.setEncryption(Message.ENCRYPTION_OTR);
|
||||||
|
} else {
|
||||||
|
message.setEncryption(Message.ENCRYPTION_PGP);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.file.setExpectedSize(size);
|
|
||||||
} else {
|
} else {
|
||||||
this.sendCancel();
|
message.setType(Message.TYPE_FILE);
|
||||||
this.cancel();
|
|
||||||
}
|
}
|
||||||
|
if (message.getType() == Message.TYPE_FILE) {
|
||||||
|
String suffix = "";
|
||||||
|
if (!fileNameElement.getContent().isEmpty()) {
|
||||||
|
String parts[] = fileNameElement.getContent().split("/");
|
||||||
|
suffix = parts[parts.length - 1];
|
||||||
|
if (message.getEncryption() == Message.ENCRYPTION_OTR && suffix.endsWith(".otr")) {
|
||||||
|
suffix = suffix.substring(0,suffix.length() - 4);
|
||||||
|
} else if (message.getEncryption() == Message.ENCRYPTION_PGP && (suffix.endsWith(".pgp") || suffix.endsWith(".gpg"))) {
|
||||||
|
suffix = suffix.substring(0,suffix.length() - 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
message.setRelativeFilePath(message.getUuid()+"_"+suffix);
|
||||||
|
}
|
||||||
|
long size = Long.parseLong(fileSize.getContent());
|
||||||
|
message.setBody(Long.toString(size));
|
||||||
|
conversation.add(message);
|
||||||
|
mXmppConnectionService.updateConversationUi();
|
||||||
|
if (size <= this.mJingleConnectionManager
|
||||||
|
.getAutoAcceptFileSize()) {
|
||||||
|
Log.d(Config.LOGTAG, "auto accepting file from "
|
||||||
|
+ packet.getFrom());
|
||||||
|
this.acceptedAutomatically = true;
|
||||||
|
this.sendAccept();
|
||||||
|
} else {
|
||||||
|
message.markUnread();
|
||||||
|
Log.d(Config.LOGTAG,
|
||||||
|
"not auto accepting new file offer with size: "
|
||||||
|
+ size
|
||||||
|
+ " allowed size:"
|
||||||
|
+ this.mJingleConnectionManager
|
||||||
|
.getAutoAcceptFileSize());
|
||||||
|
this.mXmppConnectionService.getNotificationService()
|
||||||
|
.push(message);
|
||||||
|
}
|
||||||
|
this.file = this.mXmppConnectionService.getFileBackend()
|
||||||
|
.getFile(message, false);
|
||||||
|
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
||||||
|
byte[] key = conversation.getSymmetricKey();
|
||||||
|
if (key == null) {
|
||||||
|
this.sendCancel();
|
||||||
|
this.fail();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
this.file.setKey(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.file.setExpectedSize(size);
|
||||||
} else {
|
} else {
|
||||||
this.sendCancel();
|
this.sendCancel();
|
||||||
this.cancel();
|
this.fail();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.sendCancel();
|
this.sendCancel();
|
||||||
this.cancel();
|
this.fail();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,7 +363,7 @@ public class JingleConnection implements Downloadable {
|
||||||
this.mXmppConnectionService.markMessage(this.message, Message.STATUS_OFFERED);
|
this.mXmppConnectionService.markMessage(this.message, Message.STATUS_OFFERED);
|
||||||
JinglePacket packet = this.bootstrapPacket("session-initiate");
|
JinglePacket packet = this.bootstrapPacket("session-initiate");
|
||||||
Content content = new Content(this.contentCreator, this.contentName);
|
Content content = new Content(this.contentCreator, this.contentName);
|
||||||
if (message.getType() == Message.TYPE_IMAGE) {
|
if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
|
||||||
content.setTransportId(this.transportId);
|
content.setTransportId(this.transportId);
|
||||||
this.file = this.mXmppConnectionService.getFileBackend().getFile(
|
this.file = this.mXmppConnectionService.getFileBackend().getFile(
|
||||||
message, false);
|
message, false);
|
||||||
|
@ -485,7 +494,7 @@ public class JingleConnection implements Downloadable {
|
||||||
} else {
|
} else {
|
||||||
Log.d(Config.LOGTAG, "activated connection not found");
|
Log.d(Config.LOGTAG, "activated connection not found");
|
||||||
this.sendCancel();
|
this.sendCancel();
|
||||||
this.cancel();
|
this.fail();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -532,7 +541,7 @@ public class JingleConnection implements Downloadable {
|
||||||
this.transport = connection;
|
this.transport = connection;
|
||||||
if (connection == null) {
|
if (connection == null) {
|
||||||
Log.d(Config.LOGTAG, "could not find suitable candidate");
|
Log.d(Config.LOGTAG, "could not find suitable candidate");
|
||||||
this.disconnect();
|
this.disconnectSocks5Connections();
|
||||||
if (this.initiator.equals(account.getJid())) {
|
if (this.initiator.equals(account.getJid())) {
|
||||||
this.sendFallbackToIbb();
|
this.sendFallbackToIbb();
|
||||||
}
|
}
|
||||||
|
@ -623,7 +632,7 @@ public class JingleConnection implements Downloadable {
|
||||||
reason.addChild("success");
|
reason.addChild("success");
|
||||||
packet.setReason(reason);
|
packet.setReason(reason);
|
||||||
this.sendJinglePacket(packet);
|
this.sendJinglePacket(packet);
|
||||||
this.disconnect();
|
this.disconnectSocks5Connections();
|
||||||
this.mJingleStatus = JINGLE_STATUS_FINISHED;
|
this.mJingleStatus = JINGLE_STATUS_FINISHED;
|
||||||
this.message.setStatus(Message.STATUS_RECEIVED);
|
this.message.setStatus(Message.STATUS_RECEIVED);
|
||||||
this.message.setDownloadable(null);
|
this.message.setDownloadable(null);
|
||||||
|
@ -654,8 +663,7 @@ public class JingleConnection implements Downloadable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.transportId = packet.getJingleContent().getTransportId();
|
this.transportId = packet.getJingleContent().getTransportId();
|
||||||
this.transport = new JingleInbandTransport(this.account,
|
this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize);
|
||||||
this.responder, this.transportId, this.ibbBlockSize);
|
|
||||||
this.transport.receive(file, onFileTransmissionSatusChanged);
|
this.transport.receive(file, onFileTransmissionSatusChanged);
|
||||||
JinglePacket answer = bootstrapPacket("transport-accept");
|
JinglePacket answer = bootstrapPacket("transport-accept");
|
||||||
Content content = new Content("initiator", "a-file-offer");
|
Content content = new Content("initiator", "a-file-offer");
|
||||||
|
@ -677,8 +685,7 @@ public class JingleConnection implements Downloadable {
|
||||||
this.ibbBlockSize = bs;
|
this.ibbBlockSize = bs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.transport = new JingleInbandTransport(this.account,
|
this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize);
|
||||||
this.responder, this.transportId, this.ibbBlockSize);
|
|
||||||
this.transport.connect(new OnTransportConnected() {
|
this.transport.connect(new OnTransportConnected() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -702,20 +709,51 @@ public class JingleConnection implements Downloadable {
|
||||||
this.mJingleStatus = JINGLE_STATUS_FINISHED;
|
this.mJingleStatus = JINGLE_STATUS_FINISHED;
|
||||||
this.mXmppConnectionService.markMessage(this.message,
|
this.mXmppConnectionService.markMessage(this.message,
|
||||||
Message.STATUS_SEND);
|
Message.STATUS_SEND);
|
||||||
this.disconnect();
|
this.disconnectSocks5Connections();
|
||||||
|
if (this.transport != null && this.transport instanceof JingleInbandTransport) {
|
||||||
|
this.transport.disconnect();
|
||||||
|
}
|
||||||
|
this.message.setDownloadable(null);
|
||||||
this.mJingleConnectionManager.finishConnection(this);
|
this.mJingleConnectionManager.finishConnection(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancel() {
|
public void cancel() {
|
||||||
this.mJingleStatus = JINGLE_STATUS_CANCELED;
|
this.disconnectSocks5Connections();
|
||||||
this.disconnect();
|
if (this.transport != null && this.transport instanceof JingleInbandTransport) {
|
||||||
|
this.transport.disconnect();
|
||||||
|
}
|
||||||
|
this.sendCancel();
|
||||||
|
this.mJingleConnectionManager.finishConnection(this);
|
||||||
|
if (this.responder.equals(account.getJid())) {
|
||||||
|
this.message.setDownloadable(new DownloadablePlaceholder(Downloadable.STATUS_FAILED));
|
||||||
|
if (this.file!=null) {
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
this.mXmppConnectionService.updateConversationUi();
|
||||||
|
} else {
|
||||||
|
this.mXmppConnectionService.markMessage(this.message,
|
||||||
|
Message.STATUS_SEND_FAILED);
|
||||||
|
this.message.setDownloadable(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fail() {
|
||||||
|
this.mJingleStatus = JINGLE_STATUS_FAILED;
|
||||||
|
this.disconnectSocks5Connections();
|
||||||
|
if (this.transport != null && this.transport instanceof JingleInbandTransport) {
|
||||||
|
this.transport.disconnect();
|
||||||
|
}
|
||||||
if (this.message != null) {
|
if (this.message != null) {
|
||||||
if (this.responder.equals(account.getJid())) {
|
if (this.responder.equals(account.getJid())) {
|
||||||
this.mStatus = Downloadable.STATUS_FAILED;
|
this.message.setDownloadable(new DownloadablePlaceholder(Downloadable.STATUS_FAILED));
|
||||||
|
if (this.file!=null) {
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
this.mXmppConnectionService.updateConversationUi();
|
this.mXmppConnectionService.updateConversationUi();
|
||||||
} else {
|
} else {
|
||||||
this.mXmppConnectionService.markMessage(this.message,
|
this.mXmppConnectionService.markMessage(this.message,
|
||||||
Message.STATUS_SEND_FAILED);
|
Message.STATUS_SEND_FAILED);
|
||||||
|
this.message.setDownloadable(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.mJingleConnectionManager.finishConnection(this);
|
this.mJingleConnectionManager.finishConnection(this);
|
||||||
|
@ -764,7 +802,7 @@ public class JingleConnection implements Downloadable {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void disconnect() {
|
private void disconnectSocks5Connections() {
|
||||||
Iterator<Entry<String, JingleSocks5Transport>> it = this.connections
|
Iterator<Entry<String, JingleSocks5Transport>> it = this.connections
|
||||||
.entrySet().iterator();
|
.entrySet().iterator();
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
|
@ -856,6 +894,14 @@ public class JingleConnection implements Downloadable {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateProgress(int i) {
|
||||||
|
this.mProgress = i;
|
||||||
|
if (SystemClock.elapsedRealtime() - this.mLastGuiRefresh > Config.PROGRESS_UI_UPDATE_INTERVAL) {
|
||||||
|
this.mLastGuiRefresh = SystemClock.elapsedRealtime();
|
||||||
|
mXmppConnectionService.updateConversationUi();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface OnProxyActivated {
|
interface OnProxyActivated {
|
||||||
public void success();
|
public void success();
|
||||||
|
|
||||||
|
@ -900,4 +946,29 @@ public class JingleConnection implements Downloadable {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getProgress() {
|
||||||
|
return this.mProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMimeType() {
|
||||||
|
if (this.message.getType() == Message.TYPE_FILE) {
|
||||||
|
String mime = null;
|
||||||
|
String path = this.message.getRelativeFilePath();
|
||||||
|
if (path != null && !this.message.getRelativeFilePath().isEmpty()) {
|
||||||
|
mime = URLConnection.guessContentTypeFromName(this.message.getRelativeFilePath());
|
||||||
|
if (mime!=null) {
|
||||||
|
return mime;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "image/webp";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,10 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
|
|
||||||
public void getPrimaryCandidate(Account account,
|
public void getPrimaryCandidate(Account account,
|
||||||
final OnPrimaryCandidateFound listener) {
|
final OnPrimaryCandidateFound listener) {
|
||||||
|
if (Config.NO_PROXY_LOOKUP) {
|
||||||
|
listener.onPrimaryCandidateFound(false, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!this.primaryCandidates.containsKey(account.getJid().toBareJid())) {
|
if (!this.primaryCandidates.containsKey(account.getJid().toBareJid())) {
|
||||||
String xmlns = "http://jabber.org/protocol/bytestreams";
|
String xmlns = "http://jabber.org/protocol/bytestreams";
|
||||||
final String proxy = account.getXmppConnection()
|
final String proxy = account.getXmppConnection()
|
||||||
|
|
|
@ -8,6 +8,7 @@ import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
|
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.entities.DownloadableFile;
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
|
@ -27,11 +28,15 @@ public class JingleInbandTransport extends JingleTransport {
|
||||||
|
|
||||||
private boolean established = false;
|
private boolean established = false;
|
||||||
|
|
||||||
|
private boolean connected = true;
|
||||||
|
|
||||||
private DownloadableFile file;
|
private DownloadableFile file;
|
||||||
|
private JingleConnection connection;
|
||||||
|
|
||||||
private InputStream fileInputStream = null;
|
private InputStream fileInputStream = null;
|
||||||
private OutputStream fileOutputStream;
|
private OutputStream fileOutputStream = null;
|
||||||
private long remainingSize;
|
private long remainingSize = 0;
|
||||||
|
private long fileSize = 0;
|
||||||
private MessageDigest digest;
|
private MessageDigest digest;
|
||||||
|
|
||||||
private OnFileTransmissionStatusChanged onFileTransmissionStatusChanged;
|
private OnFileTransmissionStatusChanged onFileTransmissionStatusChanged;
|
||||||
|
@ -39,16 +44,16 @@ public class JingleInbandTransport extends JingleTransport {
|
||||||
private OnIqPacketReceived onAckReceived = new OnIqPacketReceived() {
|
private OnIqPacketReceived onAckReceived = new OnIqPacketReceived() {
|
||||||
@Override
|
@Override
|
||||||
public void onIqPacketReceived(Account account, IqPacket packet) {
|
public void onIqPacketReceived(Account account, IqPacket packet) {
|
||||||
if (packet.getType() == IqPacket.TYPE_RESULT) {
|
if (connected && packet.getType() == IqPacket.TYPE_RESULT) {
|
||||||
sendNextBlock();
|
sendNextBlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public JingleInbandTransport(final Account account, final Jid counterpart,
|
public JingleInbandTransport(final JingleConnection connection, final String sid, final int blocksize) {
|
||||||
final String sid, final int blocksize) {
|
this.connection = connection;
|
||||||
this.account = account;
|
this.account = connection.getAccount();
|
||||||
this.counterpart = counterpart;
|
this.counterpart = connection.getCounterPart();
|
||||||
this.blockSize = blocksize;
|
this.blockSize = blocksize;
|
||||||
this.bufferSize = blocksize / 4;
|
this.bufferSize = blocksize / 4;
|
||||||
this.sessionId = sid;
|
this.sessionId = sid;
|
||||||
|
@ -61,7 +66,7 @@ public class JingleInbandTransport extends JingleTransport {
|
||||||
open.setAttribute("sid", this.sessionId);
|
open.setAttribute("sid", this.sessionId);
|
||||||
open.setAttribute("stanza", "iq");
|
open.setAttribute("stanza", "iq");
|
||||||
open.setAttribute("block-size", Integer.toString(this.blockSize));
|
open.setAttribute("block-size", Integer.toString(this.blockSize));
|
||||||
|
this.connected = true;
|
||||||
this.account.getXmppConnection().sendIqPacket(iq,
|
this.account.getXmppConnection().sendIqPacket(iq,
|
||||||
new OnIqPacketReceived() {
|
new OnIqPacketReceived() {
|
||||||
|
|
||||||
|
@ -92,7 +97,7 @@ public class JingleInbandTransport extends JingleTransport {
|
||||||
callback.onFileTransferAborted();
|
callback.onFileTransferAborted();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.remainingSize = file.getExpectedSize();
|
this.remainingSize = this.fileSize = file.getExpectedSize();
|
||||||
} catch (final NoSuchAlgorithmException | IOException e) {
|
} catch (final NoSuchAlgorithmException | IOException e) {
|
||||||
callback.onFileTransferAborted();
|
callback.onFileTransferAborted();
|
||||||
}
|
}
|
||||||
|
@ -104,6 +109,8 @@ public class JingleInbandTransport extends JingleTransport {
|
||||||
this.onFileTransmissionStatusChanged = callback;
|
this.onFileTransmissionStatusChanged = callback;
|
||||||
this.file = file;
|
this.file = file;
|
||||||
try {
|
try {
|
||||||
|
this.remainingSize = this.file.getSize();
|
||||||
|
this.fileSize = this.remainingSize;
|
||||||
this.digest = MessageDigest.getInstance("SHA-1");
|
this.digest = MessageDigest.getInstance("SHA-1");
|
||||||
this.digest.reset();
|
this.digest.reset();
|
||||||
fileInputStream = this.file.createInputStream();
|
fileInputStream = this.file.createInputStream();
|
||||||
|
@ -111,12 +118,33 @@ public class JingleInbandTransport extends JingleTransport {
|
||||||
callback.onFileTransferAborted();
|
callback.onFileTransferAborted();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.sendNextBlock();
|
if (this.connected) {
|
||||||
|
this.sendNextBlock();
|
||||||
|
}
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
callback.onFileTransferAborted();
|
callback.onFileTransferAborted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disconnect() {
|
||||||
|
this.connected = false;
|
||||||
|
if (this.fileOutputStream != null) {
|
||||||
|
try {
|
||||||
|
this.fileOutputStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.fileInputStream != null) {
|
||||||
|
try {
|
||||||
|
this.fileInputStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void sendNextBlock() {
|
private void sendNextBlock() {
|
||||||
byte[] buffer = new byte[this.bufferSize];
|
byte[] buffer = new byte[this.bufferSize];
|
||||||
try {
|
try {
|
||||||
|
@ -126,6 +154,7 @@ public class JingleInbandTransport extends JingleTransport {
|
||||||
fileInputStream.close();
|
fileInputStream.close();
|
||||||
this.onFileTransmissionStatusChanged.onFileTransmitted(file);
|
this.onFileTransmissionStatusChanged.onFileTransmitted(file);
|
||||||
} else {
|
} else {
|
||||||
|
this.remainingSize -= count;
|
||||||
this.digest.update(buffer);
|
this.digest.update(buffer);
|
||||||
String base64 = Base64.encodeToString(buffer, Base64.NO_WRAP);
|
String base64 = Base64.encodeToString(buffer, Base64.NO_WRAP);
|
||||||
IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
|
IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
|
||||||
|
@ -140,6 +169,7 @@ public class JingleInbandTransport extends JingleTransport {
|
||||||
this.account.getXmppConnection().sendIqPacket(iq,
|
this.account.getXmppConnection().sendIqPacket(iq,
|
||||||
this.onAckReceived);
|
this.onAckReceived);
|
||||||
this.seq++;
|
this.seq++;
|
||||||
|
connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
this.onFileTransmissionStatusChanged.onFileTransferAborted();
|
this.onFileTransmissionStatusChanged.onFileTransferAborted();
|
||||||
|
@ -155,6 +185,7 @@ public class JingleInbandTransport extends JingleTransport {
|
||||||
}
|
}
|
||||||
this.remainingSize -= buffer.length;
|
this.remainingSize -= buffer.length;
|
||||||
|
|
||||||
|
|
||||||
this.fileOutputStream.write(buffer);
|
this.fileOutputStream.write(buffer);
|
||||||
|
|
||||||
this.digest.update(buffer);
|
this.digest.update(buffer);
|
||||||
|
@ -163,6 +194,8 @@ public class JingleInbandTransport extends JingleTransport {
|
||||||
fileOutputStream.flush();
|
fileOutputStream.flush();
|
||||||
fileOutputStream.close();
|
fileOutputStream.close();
|
||||||
this.onFileTransmissionStatusChanged.onFileTransmitted(file);
|
this.onFileTransmissionStatusChanged.onFileTransmitted(file);
|
||||||
|
} else {
|
||||||
|
connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
this.onFileTransmissionStatusChanged.onFileTransferAborted();
|
this.onFileTransmissionStatusChanged.onFileTransferAborted();
|
||||||
|
@ -173,13 +206,14 @@ public class JingleInbandTransport extends JingleTransport {
|
||||||
if (payload.getName().equals("open")) {
|
if (payload.getName().equals("open")) {
|
||||||
if (!established) {
|
if (!established) {
|
||||||
established = true;
|
established = true;
|
||||||
|
connected = true;
|
||||||
this.account.getXmppConnection().sendIqPacket(
|
this.account.getXmppConnection().sendIqPacket(
|
||||||
packet.generateRespone(IqPacket.TYPE_RESULT), null);
|
packet.generateRespone(IqPacket.TYPE_RESULT), null);
|
||||||
} else {
|
} else {
|
||||||
this.account.getXmppConnection().sendIqPacket(
|
this.account.getXmppConnection().sendIqPacket(
|
||||||
packet.generateRespone(IqPacket.TYPE_ERROR), null);
|
packet.generateRespone(IqPacket.TYPE_ERROR), null);
|
||||||
}
|
}
|
||||||
} else if (payload.getName().equals("data")) {
|
} else if (connected && payload.getName().equals("data")) {
|
||||||
this.receiveNextBlock(payload.getContent());
|
this.receiveNextBlock(payload.getContent());
|
||||||
this.account.getXmppConnection().sendIqPacket(
|
this.account.getXmppConnection().sendIqPacket(
|
||||||
packet.generateRespone(IqPacket.TYPE_RESULT), null);
|
packet.generateRespone(IqPacket.TYPE_RESULT), null);
|
||||||
|
|
|
@ -15,6 +15,7 @@ import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
|
|
||||||
public class JingleSocks5Transport extends JingleTransport {
|
public class JingleSocks5Transport extends JingleTransport {
|
||||||
private JingleCandidate candidate;
|
private JingleCandidate candidate;
|
||||||
|
private JingleConnection connection;
|
||||||
private String destination;
|
private String destination;
|
||||||
private OutputStream outputStream;
|
private OutputStream outputStream;
|
||||||
private InputStream inputStream;
|
private InputStream inputStream;
|
||||||
|
@ -25,6 +26,7 @@ public class JingleSocks5Transport extends JingleTransport {
|
||||||
public JingleSocks5Transport(JingleConnection jingleConnection,
|
public JingleSocks5Transport(JingleConnection jingleConnection,
|
||||||
JingleCandidate candidate) {
|
JingleCandidate candidate) {
|
||||||
this.candidate = candidate;
|
this.candidate = candidate;
|
||||||
|
this.connection = jingleConnection;
|
||||||
try {
|
try {
|
||||||
MessageDigest mDigest = MessageDigest.getInstance("SHA-1");
|
MessageDigest mDigest = MessageDigest.getInstance("SHA-1");
|
||||||
StringBuilder destBuilder = new StringBuilder();
|
StringBuilder destBuilder = new StringBuilder();
|
||||||
|
@ -102,11 +104,15 @@ public class JingleSocks5Transport extends JingleTransport {
|
||||||
callback.onFileTransferAborted();
|
callback.onFileTransferAborted();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
long size = file.getSize();
|
||||||
|
long transmitted = 0;
|
||||||
int count;
|
int count;
|
||||||
byte[] buffer = new byte[8192];
|
byte[] buffer = new byte[8192];
|
||||||
while ((count = fileInputStream.read(buffer)) > 0) {
|
while ((count = fileInputStream.read(buffer)) > 0) {
|
||||||
outputStream.write(buffer, 0, count);
|
outputStream.write(buffer, 0, count);
|
||||||
digest.update(buffer, 0, count);
|
digest.update(buffer, 0, count);
|
||||||
|
transmitted += count;
|
||||||
|
connection.updateProgress((int) ((((double) transmitted) / size) * 100));
|
||||||
}
|
}
|
||||||
outputStream.flush();
|
outputStream.flush();
|
||||||
file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
|
file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
|
||||||
|
@ -151,6 +157,7 @@ public class JingleSocks5Transport extends JingleTransport {
|
||||||
callback.onFileTransferAborted();
|
callback.onFileTransferAborted();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
double size = file.getExpectedSize();
|
||||||
long remainingSize = file.getExpectedSize();
|
long remainingSize = file.getExpectedSize();
|
||||||
byte[] buffer = new byte[8192];
|
byte[] buffer = new byte[8192];
|
||||||
int count = buffer.length;
|
int count = buffer.length;
|
||||||
|
@ -164,6 +171,7 @@ public class JingleSocks5Transport extends JingleTransport {
|
||||||
digest.update(buffer, 0, count);
|
digest.update(buffer, 0, count);
|
||||||
remainingSize -= count;
|
remainingSize -= count;
|
||||||
}
|
}
|
||||||
|
connection.updateProgress((int) (((size - remainingSize) / size) * 100));
|
||||||
}
|
}
|
||||||
fileOutputStream.flush();
|
fileOutputStream.flush();
|
||||||
fileOutputStream.close();
|
fileOutputStream.close();
|
||||||
|
@ -189,6 +197,20 @@ public class JingleSocks5Transport extends JingleTransport {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void disconnect() {
|
public void disconnect() {
|
||||||
|
if (this.outputStream != null) {
|
||||||
|
try {
|
||||||
|
this.outputStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.inputStream != null) {
|
||||||
|
try {
|
||||||
|
this.inputStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
if (this.socket != null) {
|
if (this.socket != null) {
|
||||||
try {
|
try {
|
||||||
this.socket.close();
|
this.socket.close();
|
||||||
|
|
|
@ -10,4 +10,6 @@ public abstract class JingleTransport {
|
||||||
|
|
||||||
public abstract void send(final DownloadableFile file,
|
public abstract void send(final DownloadableFile file,
|
||||||
final OnFileTransmissionStatusChanged callback);
|
final OnFileTransmissionStatusChanged callback);
|
||||||
|
|
||||||
|
public abstract void disconnect();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
android:title="@string/attach_take_picture"/>
|
android:title="@string/attach_take_picture"/>
|
||||||
<item
|
<item
|
||||||
android:id="@+id/attach_record_voice"
|
android:id="@+id/attach_record_voice"
|
||||||
android:title="@string/attach_record_voice"
|
android:title="@string/choose_file"/>
|
||||||
android:visible="false"/>
|
|
||||||
|
|
||||||
</menu>
|
</menu>
|
|
@ -16,5 +16,8 @@
|
||||||
<item
|
<item
|
||||||
android:id="@+id/download_image"
|
android:id="@+id/download_image"
|
||||||
android:title="@string/download_image"/>
|
android:title="@string/download_image"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/cancel_transmission"
|
||||||
|
android:title="@string/cancel_transmission" />
|
||||||
|
|
||||||
</menu>
|
</menu>
|
|
@ -58,7 +58,7 @@
|
||||||
<string name="add_contact">Add contact</string>
|
<string name="add_contact">Add contact</string>
|
||||||
<string name="send_failed">delivery failed</string>
|
<string name="send_failed">delivery failed</string>
|
||||||
<string name="send_rejected">rejected</string>
|
<string name="send_rejected">rejected</string>
|
||||||
<string name="receiving_image">Receiving image file. Please wait…</string>
|
<string name="receiving_image">Receiving image file (%1$d%%)</string>
|
||||||
<string name="preparing_image">Preparing image for transmission</string>
|
<string name="preparing_image">Preparing image for transmission</string>
|
||||||
<string name="action_clear_history">Clear history</string>
|
<string name="action_clear_history">Clear history</string>
|
||||||
<string name="clear_conversation_history">Clear Conversation History</string>
|
<string name="clear_conversation_history">Clear Conversation History</string>
|
||||||
|
@ -332,4 +332,16 @@
|
||||||
<string name="touch_to_disable">Touch to disable foreground service</string>
|
<string name="touch_to_disable">Touch to disable foreground service</string>
|
||||||
<string name="pref_keep_foreground_service">Keep service in foreground</string>
|
<string name="pref_keep_foreground_service">Keep service in foreground</string>
|
||||||
<string name="pref_keep_foreground_service_summary">Prevents the operating system from killing your connection</string>
|
<string name="pref_keep_foreground_service_summary">Prevents the operating system from killing your connection</string>
|
||||||
|
<string name="choose_file">Choose file</string>
|
||||||
|
<string name="receiving_file">Receiving %1$s file (%2$d%% completed)</string>
|
||||||
|
<string name="download_file">Download %s file</string>
|
||||||
|
<string name="open_file">Open %s file</string>
|
||||||
|
<string name="sending_file">sending (%1$d%% completed)</string>
|
||||||
|
<string name="preparing_file">Preparing file for transmission</string>
|
||||||
|
<string name="file_offered_for_download">File offered for download</string>
|
||||||
|
<string name="file">%s file</string>
|
||||||
|
<string name="cancel_transmission">Cancel transmission</string>
|
||||||
|
<string name="file_transmission_failed">file transmission failed</string>
|
||||||
|
<string name="file_deleted">The file has been deleted</string>
|
||||||
|
<string name="no_application_found_to_open_file">No application found to open file</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue