basic arbitrary file transfer

This commit is contained in:
iNPUTmice 2014-11-13 21:04:05 +01:00
parent 4c504dea7a
commit 7a90ca429b
13 changed files with 334 additions and 138 deletions

View file

@ -2,7 +2,7 @@ package eu.siacs.conversations.entities;
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 static final int STATUS_UNKNOWN = 0x200;
@ -18,4 +18,8 @@ public interface Downloadable {
public int getStatus();
public long getFileSize();
public int getProgress();
public String getMimeType();
}

View file

@ -6,6 +6,7 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLConnection;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
@ -28,6 +29,7 @@ public class DownloadableFile extends File {
private long expectedSize = 0;
private String sha1sum;
private Key aeskey;
private String mime;
private byte[] iv = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf };
@ -52,6 +54,16 @@ public class DownloadableFile extends File {
}
}
public String getMimeType() {
if (mime==null) {
mime = URLConnection.guessContentTypeFromName(this.getAbsolutePath());
if (mime == null) {
mime = "";
}
}
return mime;
}
public void setExpectedSize(long size) {
this.expectedSize = size;
}

View file

@ -32,7 +32,7 @@ public class Message extends AbstractEntity {
public static final int TYPE_TEXT = 0;
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_PRIVATE = 4;
@ -45,6 +45,7 @@ public class Message extends AbstractEntity {
public static String STATUS = "status";
public static String TYPE = "type";
public static String REMOTE_MSG_ID = "remoteMsgId";
public static String RELATIVE_FILE_PATH = "relativeFilePath";
public boolean markable = false;
protected String conversationUuid;
protected Jid counterpart;
@ -55,6 +56,7 @@ public class Message extends AbstractEntity {
protected int encryption;
protected int status;
protected int type;
protected String relativeFilePath;
protected boolean read = true;
protected String remoteMsgId = null;
protected Conversation conversation = null;
@ -74,13 +76,13 @@ public class Message extends AbstractEntity {
this(java.util.UUID.randomUUID().toString(), conversation.getUuid(),
conversation.getContactJid().toBareJid(), null, body, System
.currentTimeMillis(), encryption,
status, TYPE_TEXT, null);
status, TYPE_TEXT, null,null);
this.conversation = conversation;
}
public Message(final String uuid, final String conversationUUid, final Jid counterpart,
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.conversationUuid = conversationUUid;
this.counterpart = counterpart;
@ -91,6 +93,7 @@ public class Message extends AbstractEntity {
this.status = status;
this.type = type;
this.remoteMsgId = remoteMsgId;
this.relativeFilePath = relativeFilePath;
}
public static Message fromCursor(Cursor cursor) {
@ -114,7 +117,8 @@ public class Message extends AbstractEntity {
cursor.getInt(cursor.getColumnIndex(ENCRYPTION)),
cursor.getInt(cursor.getColumnIndex(STATUS)),
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) {
@ -141,6 +145,7 @@ public class Message extends AbstractEntity {
values.put(STATUS, status);
values.put(TYPE, type);
values.put(REMOTE_MSG_ID, remoteMsgId);
values.put(RELATIVE_FILE_PATH, relativeFilePath);
return values;
}
@ -205,6 +210,14 @@ public class Message extends AbstractEntity {
this.status = status;
}
public void setRelativeFilePath(String path) {
this.relativeFilePath = path;
}
public String getRelativeFilePath() {
return this.relativeFilePath;
}
public String getRemoteMsgId() {
return this.remoteMsgId;
}
@ -376,14 +389,14 @@ public class Message extends AbstractEntity {
}
String[] extensionParts = filename.split("\\.");
if (extensionParts.length == 2
&& Arrays.asList(Downloadable.VALID_EXTENSIONS).contains(
&& Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains(
extensionParts[extensionParts.length - 1])) {
return true;
} else if (extensionParts.length == 3
&& Arrays
.asList(Downloadable.VALID_CRYPTO_EXTENSIONS)
.contains(extensionParts[extensionParts.length - 1])
&& Arrays.asList(Downloadable.VALID_EXTENSIONS).contains(
&& Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains(
extensionParts[extensionParts.length - 2])) {
return true;
} else {

View file

@ -37,6 +37,7 @@ public class HttpConnection implements Downloadable {
private DownloadableFile file;
private int mStatus = Downloadable.STATUS_UNKNOWN;
private boolean acceptedAutomatically = false;
private int mProgress = 0;
public HttpConnection(HttpConnectionManager manager) {
this.mHttpConnectionManager = manager;
@ -235,10 +236,14 @@ public class HttpConnection implements Downloadable {
if (os == null) {
throw new IOException();
}
long transmitted = 0;
long expected = file.getExpectedSize();
int count = -1;
byte[] buffer = new byte[1024];
while ((count = is.read(buffer)) != -1) {
transmitted += count;
os.write(buffer, 0, count);
mProgress = (int) (expected * 100 / transmitted);
}
os.flush();
os.close();
@ -272,4 +277,14 @@ public class HttpConnection implements Downloadable {
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 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 "
+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
@ -64,6 +64,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ " TEXT, " + Message.TRUE_COUNTERPART + " TEXT,"
+ Message.BODY + " TEXT, " + Message.ENCRYPTION + " NUMBER, "
+ Message.STATUS + " NUMBER," + Message.TYPE + " NUMBER, "
+ Message.RELATIVE_FILE_PATH + " TEXT, "
+ Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
+ Message.CONVERSATION + ") REFERENCES "
+ Conversation.TABLENAME + "(" + Conversation.UUID
@ -110,6 +111,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
+ 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) {

View file

@ -2,11 +2,13 @@ package eu.siacs.conversations.persistance;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLConnection;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@ -14,6 +16,7 @@ import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import android.content.ContentResolver;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@ -53,25 +56,34 @@ public class FileBackend {
}
public DownloadableFile getFile(Message message, boolean decrypted) {
StringBuilder filename = new StringBuilder();
filename.append(getConversationsDirectory());
filename.append(message.getUuid());
if ((decrypted) || (message.getEncryption() == Message.ENCRYPTION_NONE)) {
filename.append(".webp");
String path = message.getRelativeFilePath();
if (path != null && !path.isEmpty()) {
if (path.startsWith("/")) {
return new DownloadableFile(path);
} else {
return new DownloadableFile(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)+"/"+path);
}
} else {
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
StringBuilder filename = new StringBuilder();
filename.append(getConversationsDirectory());
filename.append(message.getUuid());
if ((decrypted) || (message.getEncryption() == Message.ENCRYPTION_NONE)) {
filename.append(".webp");
} else {
filename.append(".webp.pgp");
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
filename.append(".webp");
} else {
filename.append(".webp.pgp");
}
}
return new DownloadableFile(filename.toString());
}
return new DownloadableFile(filename.toString());
}
public static String getConversationsDirectory() {
return Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES).getAbsolutePath()
+ "/Conversations/";
Environment.DIRECTORY_PICTURES).getAbsolutePath()
+ "/Conversations/";
}
public Bitmap resize(Bitmap originalBitmap, int size) {
@ -103,13 +115,34 @@ public class FileBackend {
return Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true);
}
public String getOriginalPath(Uri uri) {
String path = null;
if (uri.getScheme().equals("file")) {
path = uri.getPath();
} else {
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 copyImageToPrivateStorage(Message message, Uri image)
throws ImageCopyException {
throws FileCopyException {
return this.copyImageToPrivateStorage(message, image, 0);
}
private DownloadableFile copyImageToPrivateStorage(Message message,
Uri image, int sampleSize) throws ImageCopyException {
Uri image, int sampleSize) throws FileCopyException {
try {
InputStream is = mXmppConnectionService.getContentResolver()
.openInputStream(image);
@ -125,7 +158,7 @@ public class FileBackend {
originalBitmap = BitmapFactory.decodeStream(is, null, options);
is.close();
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);
originalBitmap = null;
@ -137,7 +170,7 @@ public class FileBackend {
boolean success = scalledBitmap.compress(
Bitmap.CompressFormat.WEBP, 75, os);
if (!success) {
throw new ImageCopyException(R.string.error_compressing_image);
throw new FileCopyException(R.string.error_compressing_image);
}
os.flush();
os.close();
@ -147,18 +180,18 @@ public class FileBackend {
message.setBody(Long.toString(size) + ',' + width + ',' + height);
return file;
} catch (FileNotFoundException e) {
throw new ImageCopyException(R.string.error_file_not_found);
throw new FileCopyException(R.string.error_file_not_found);
} catch (IOException e) {
throw new ImageCopyException(R.string.error_io_exception);
throw new FileCopyException(R.string.error_io_exception);
} catch (SecurityException e) {
throw new ImageCopyException(
throw new FileCopyException(
R.string.error_security_exception_during_image_copy);
} catch (OutOfMemoryError e) {
++sampleSize;
if (sampleSize <= 3) {
return copyImageToPrivateStorage(message, image, sampleSize);
} else {
throw new ImageCopyException(R.string.error_out_of_memory);
throw new FileCopyException(R.string.error_out_of_memory);
}
}
}
@ -400,11 +433,11 @@ public class FileBackend {
return Uri.parse("file://" + file.getAbsolutePath());
}
public class ImageCopyException extends Exception {
public class FileCopyException extends Exception {
private static final long serialVersionUID = -1010013599132881427L;
private int resId;
public ImageCopyException(int resId) {
public FileCopyException(int resId) {
this.resId = resId;
}

View file

@ -56,6 +56,7 @@ import eu.siacs.conversations.entities.Bookmark;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Downloadable;
import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.MucOptions.OnRenameListener;
@ -294,6 +295,27 @@ public class XmppConnectionService extends Service {
return this.mAvatarService;
}
public Message attachFileToConversation(Conversation conversation, final Uri uri) {
String path = getFileBackend().getOriginalPath(uri);
if (path!=null) {
Log.d(Config.LOGTAG,"file path : "+path);
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);
message.setRelativeFilePath(path);
return message;
}
return null;
}
public Message attachImageToConversation(final Conversation conversation,
final Uri uri, final UiCallback<Message> callback) {
final Message message;
@ -312,13 +334,14 @@ public class XmppConnectionService extends Service {
@Override
public void run() {
try {
getFileBackend().copyImageToPrivateStorage(message, uri);
DownloadableFile file = getFileBackend().copyImageToPrivateStorage(message, uri);
message.setRelativeFilePath(file.getName());
if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) {
getPgpEngine().encrypt(message, callback);
} else {
callback.success(message);
}
} catch (FileBackend.ImageCopyException e) {
} catch (FileBackend.FileCopyException e) {
callback.error(e.getResId(), message);
}
}
@ -552,7 +575,7 @@ public class XmppConnectionService extends Service {
boolean send = false;
if (account.getStatus() == Account.STATUS_ONLINE
&& 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.getEncryption() == Message.ENCRYPTION_OTR) {
if (!conv.hasValidOtrSession()) {
@ -1988,5 +2011,15 @@ public class XmppConnectionService extends Service {
return 0;
}
@Override
public int getProgress() {
return 0;
}
@Override
public String getMimeType() {
return "";
}
}
}

View file

@ -15,6 +15,7 @@ import android.os.SystemClock;
import android.provider.MediaStore;
import android.support.v4.widget.SlidingPaneLayout;
import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
@ -31,6 +32,7 @@ import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
@ -52,13 +54,14 @@ public class ConversationActivity extends XmppActivity implements
public static final int REQUEST_SEND_MESSAGE = 0x0201;
public static final int REQUEST_DECRYPT_PGP = 0x0202;
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_RECORD_AUDIO = 0x0205;
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_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_PANEL_OPEN = "state_panel_open";
private static final String STATE_PENDING_URI = "state_pending_uri";
@ -66,6 +69,7 @@ public class ConversationActivity extends XmppActivity implements
private String mOpenConverstaion = null;
private boolean mPanelOpen = true;
private Uri mPendingImageUri = null;
private Uri mPendingFileUri = null;
private View mContentView;
@ -306,13 +310,16 @@ public class ConversationActivity extends XmppActivity implements
Intent attachFileIntent = new Intent();
attachFileIntent.setType("image/*");
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.setAction(Intent.ACTION_GET_CONTENT);
Intent chooser = Intent.createChooser(attachFileIntent,
getString(R.string.attach_file));
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);
break;
case R.id.attach_record_voice:
attachFile(ATTACHMENT_CHOICE_RECORD_VOICE);
attachFile(ATTACHMENT_CHOICE_CHOOSE_FILE);
break;
}
return false;
@ -675,14 +682,17 @@ public class ConversationActivity extends XmppActivity implements
} else {
showConversationsOverview();
mPendingImageUri = null;
mPendingFileUri = null;
setSelectedConversation(conversationList.get(0));
this.mConversationFragment.reInit(getSelectedConversation());
}
if (mPendingImageUri != null) {
attachImageToConversation(getSelectedConversation(),
mPendingImageUri);
attachImageToConversation(getSelectedConversation(),mPendingImageUri);
mPendingImageUri = null;
} else if (mPendingFileUri != null) {
attachFileToConversation(getSelectedConversation(),mPendingFileUri);
mPendingFileUri = null;
}
ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
setIntent(new Intent());
@ -726,13 +736,20 @@ public class ConversationActivity extends XmppActivity implements
selectedFragment.hideSnackbar();
selectedFragment.updateMessages();
}
} else if (requestCode == REQUEST_ATTACH_FILE_DIALOG) {
} else if (requestCode == REQUEST_ATTACH_IMAGE_DIALOG) {
mPendingImageUri = data.getData();
if (xmppConnectionServiceBound) {
attachImageToConversation(getSelectedConversation(),
mPendingImageUri);
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 == ATTACHMENT_CHOICE_CHOOSE_IMAGE) {
@ -754,9 +771,6 @@ public class ConversationActivity extends XmppActivity implements
Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(mPendingImageUri);
sendBroadcast(intent);
} else if (requestCode == REQUEST_RECORD_AUDIO) {
attachAudioToConversation(getSelectedConversation(),
data.getData());
}
} else {
if (requestCode == REQUEST_IMAGE_CAPTURE) {
@ -765,8 +779,10 @@ public class ConversationActivity extends XmppActivity implements
}
}
private void attachAudioToConversation(Conversation conversation, Uri uri) {
private void attachFileToConversation(Conversation conversation, Uri uri) {
Log.d(Config.LOGTAG, "attachFileToConversation");
Message message = xmppConnectionService.attachFileToConversation(conversation,uri);
xmppConnectionService.sendMessage(message);
}
private void attachImageToConversation(Conversation conversation, Uri uri) {

View file

@ -2,15 +2,18 @@ package eu.siacs.conversations.ui.adapter;
import android.content.Intent;
import android.graphics.Typeface;
import android.net.Uri;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.webkit.MimeTypeMap;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
@ -18,6 +21,9 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.List;
import eu.siacs.conversations.Config;
@ -25,6 +31,7 @@ import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Downloadable;
import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.Message.ImageParams;
import eu.siacs.conversations.ui.ConversationActivity;
@ -181,13 +188,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) {
viewHolder.download_button.setVisibility(View.GONE);
}
viewHolder.image.setVisibility(View.GONE);
viewHolder.messageBody.setVisibility(View.VISIBLE);
viewHolder.messageBody.setText(getContext().getString(r));
viewHolder.messageBody.setText(text);
viewHolder.messageBody.setTextColor(activity.getSecondaryTextColor());
viewHolder.messageBody.setTypeface(null, Typeface.ITALIC);
viewHolder.messageBody.setTextIsSelectable(false);
@ -252,11 +259,11 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
private void displayDownloadableMessage(ViewHolder viewHolder,
final Message message, int resid) {
final Message message, String text) {
viewHolder.image.setVisibility(View.GONE);
viewHolder.messageBody.setVisibility(View.GONE);
viewHolder.download_button.setVisibility(View.VISIBLE);
viewHolder.download_button.setText(resid);
viewHolder.download_button.setText(text);
viewHolder.download_button.setOnClickListener(new OnClickListener() {
@Override
@ -267,6 +274,21 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.download_button.setOnLongClickListener(openContextMenu);
}
private void displayOpenableMessage(ViewHolder viewHolder,final Message 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,activity.xmppConnectionService.getFileBackend().getFile(message).getMimeType()));
viewHolder.download_button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
openDonwloadable(message);
}
});
viewHolder.download_button.setOnLongClickListener(openContextMenu);
}
private void displayImageMessage(ViewHolder viewHolder,
final Message message) {
if (viewHolder.download_button != null) {
@ -455,42 +477,46 @@ public class MessageAdapter extends ArrayAdapter<Message> {
});
}
if (item.getType() == Message.TYPE_IMAGE
|| item.getDownloadable() != null) {
if (item.getDownloadable() != null) {
Downloadable d = item.getDownloadable();
if (d != null && d.getStatus() == Downloadable.STATUS_DOWNLOADING) {
displayInfoMessage(viewHolder, R.string.receiving_image);
} else if (d != null
&& 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);
if (d.getStatus() == Downloadable.STATUS_DOWNLOADING) {
if (item.getType() == Message.TYPE_FILE) {
displayInfoMessage(viewHolder,activity.getString(R.string.receiving_file,d.getMimeType(),d.getProgress()));
} else {
displayInfoMessage(viewHolder,activity.getString(R.string.receiving_image,d.getProgress()));
}
} else if (d.getStatus() == Downloadable.STATUS_CHECKING) {
displayInfoMessage(viewHolder,activity.getString(R.string.checking_image));
} else if (d.getStatus() == Downloadable.STATUS_DELETED) {
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) {
displayInfoMessage(viewHolder, activity.getString(R.string.image_transmission_failed));
}
} else if (item.getType() == Message.TYPE_IMAGE) {
if (item.getEncryption() == Message.ENCRYPTION_PGP) {
displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message));
} else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
displayDecryptionFailed(viewHolder);
} else {
displayImageMessage(viewHolder, item);
}
} else if (item.getType() == Message.TYPE_FILE) {
displayOpenableMessage(viewHolder,item);
} else {
if (item.getEncryption() == Message.ENCRYPTION_PGP) {
if (activity.hasPgp()) {
displayInfoMessage(viewHolder, R.string.encrypted_message);
displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message));
} else {
displayInfoMessage(viewHolder,
R.string.install_openkeychain);
activity.getString(R.string.install_openkeychain));
if (viewHolder != null) {
viewHolder.message_box
.setOnClickListener(new OnClickListener() {
@ -524,6 +550,13 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
}
public void openDonwloadable(Message message) {
DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), file.getMimeType());
getContext().startActivity(intent);
}
public interface OnContactPictureClicked {
public void onContactPictureClicked(Message message);
}

View file

@ -11,6 +11,7 @@ import java.util.concurrent.ConcurrentHashMap;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.SystemClock;
import android.util.Log;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
@ -29,9 +30,6 @@ import eu.siacs.conversations.xmpp.stanzas.IqPacket;
public class JingleConnection implements Downloadable {
private final String[] extensions = { "webp", "jpeg", "jpg", "png" };
private final String[] cryptoExtensions = { "pgp", "gpg", "otr" };
private JingleConnectionManager mJingleConnectionManager;
private XmppConnectionService mXmppConnectionService;
@ -62,6 +60,9 @@ public class JingleConnection implements Downloadable {
private String contentName;
private String contentCreator;
private int mProgress = 0;
private long mLastGuiRefresh = 0;
private boolean receivedCandidate = false;
private boolean sentCandidate = false;
@ -258,7 +259,6 @@ public class JingleConnection implements Downloadable {
packet.getFrom().toBareJid(), false);
this.message = new Message(conversation, "", Message.ENCRYPTION_NONE);
this.message.setStatus(Message.STATUS_RECEIVED);
this.message.setType(Message.TYPE_IMAGE);
this.mStatus = Downloadable.STATUS_OFFER;
this.message.setDownloadable(this);
final Jid from = packet.getFrom();
@ -278,68 +278,71 @@ public class JingleConnection implements Downloadable {
Element fileSize = fileOffer.findChild("size");
Element fileNameElement = fileOffer.findChild("name");
if (fileNameElement != null) {
boolean supportedFile = false;
String[] filename = fileNameElement.getContent()
.toLowerCase(Locale.US).split("\\.");
if (Arrays.asList(this.extensions).contains(
if (Arrays.asList(VALID_IMAGE_EXTENSIONS).contains(
filename[filename.length - 1])) {
supportedFile = true;
} else if (Arrays.asList(this.cryptoExtensions).contains(
message.setType(Message.TYPE_IMAGE);
} else if (Arrays.asList(VALID_CRYPTO_EXTENSIONS).contains(
filename[filename.length - 1])) {
if (filename.length == 3) {
if (Arrays.asList(this.extensions).contains(
if (Arrays.asList(VALID_IMAGE_EXTENSIONS).contains(
filename[filename.length - 2])) {
supportedFile = true;
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;
message.setType(Message.TYPE_IMAGE);
} 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 {
this.sendCancel();
this.cancel();
message.setType(Message.TYPE_FILE);
}
if (message.getType() == Message.TYPE_FILE) {
String suffix = "";
if (!fileNameElement.getContent().isEmpty()) {
String parts[] = fileNameElement.getContent().split("/");
suffix = parts[parts.length - 1];
}
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.cancel();
return;
} else {
this.file.setKey(key);
}
}
this.file.setExpectedSize(size);
} else {
this.sendCancel();
this.cancel();
@ -354,7 +357,7 @@ public class JingleConnection implements Downloadable {
this.mXmppConnectionService.markMessage(this.message, Message.STATUS_OFFERED);
JinglePacket packet = this.bootstrapPacket("session-initiate");
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);
this.file = this.mXmppConnectionService.getFileBackend().getFile(
message, false);
@ -856,6 +859,14 @@ public class JingleConnection implements Downloadable {
return null;
}
public void updateProgress(int i) {
this.mProgress = i;
if (SystemClock.elapsedRealtime() - this.mLastGuiRefresh > 1000) {
this.mLastGuiRefresh = SystemClock.elapsedRealtime();
mXmppConnectionService.updateConversationUi();
}
}
interface OnProxyActivated {
public void success();
@ -900,4 +911,14 @@ public class JingleConnection implements Downloadable {
return 0;
}
}
@Override
public int getProgress() {
return this.mProgress;
}
@Override
public String getMimeType() {
return this.file.getMimeType();
}
}

View file

@ -15,6 +15,7 @@ import eu.siacs.conversations.utils.CryptoHelper;
public class JingleSocks5Transport extends JingleTransport {
private JingleCandidate candidate;
private JingleConnection connection;
private String destination;
private OutputStream outputStream;
private InputStream inputStream;
@ -25,6 +26,7 @@ public class JingleSocks5Transport extends JingleTransport {
public JingleSocks5Transport(JingleConnection jingleConnection,
JingleCandidate candidate) {
this.candidate = candidate;
this.connection = jingleConnection;
try {
MessageDigest mDigest = MessageDigest.getInstance("SHA-1");
StringBuilder destBuilder = new StringBuilder();
@ -102,11 +104,15 @@ public class JingleSocks5Transport extends JingleTransport {
callback.onFileTransferAborted();
return;
}
long size = file.getSize();
double transmitted = 0;
int count;
byte[] buffer = new byte[8192];
while ((count = fileInputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, count);
digest.update(buffer, 0, count);
transmitted += count;
connection.updateProgress((int) (((transmitted) / size) * 100));
}
outputStream.flush();
file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
@ -151,6 +157,7 @@ public class JingleSocks5Transport extends JingleTransport {
callback.onFileTransferAborted();
return;
}
double size = file.getExpectedSize();
long remainingSize = file.getExpectedSize();
byte[] buffer = new byte[8192];
int count = buffer.length;
@ -164,6 +171,7 @@ public class JingleSocks5Transport extends JingleTransport {
digest.update(buffer, 0, count);
remainingSize -= count;
}
connection.updateProgress((int) (((size - remainingSize) / size) * 100));
}
fileOutputStream.flush();
fileOutputStream.close();

View file

@ -9,7 +9,6 @@
android:title="@string/attach_take_picture"/>
<item
android:id="@+id/attach_record_voice"
android:title="@string/attach_record_voice"
android:visible="false"/>
android:title="@string/choose_file"/>
</menu>

View file

@ -58,7 +58,7 @@
<string name="add_contact">Add contact</string>
<string name="send_failed">delivery failed</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="action_clear_history">Clear history</string>
<string name="clear_conversation_history">Clear Conversation History</string>
@ -311,4 +311,8 @@
<string name="scan_qr_code">Scan QR code</string>
<string name="show_qr_code">Show QR code</string>
<string name="account_details">Account details</string>
<string name="choose_file">Choose file</string>
<string name="receiving_file">Receiving %1$s file (%2$d%%)</string>
<string name="download_file">Download %s file</string>
<string name="open_file">Open %s file</string>
</resources>