Merge branch 'feature/file_transfer' into development

Conflicts:
	src/main/res/values/strings.xml
This commit is contained in:
iNPUTmice 2014-11-15 15:52:15 +01:00
commit 35bf13f5ef
23 changed files with 775 additions and 278 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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