diff --git a/res/layout/message_recieved.xml b/res/layout/message_recieved.xml
index 62f4f00ac..32a26c3a1 100644
--- a/res/layout/message_recieved.xml
+++ b/res/layout/message_recieved.xml
@@ -26,7 +26,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
- android:maxHeight="300dp"
+ android:maxHeight="288dp"
+ android:maxWidth="288dp"
/>
thumbnailCache;
+
public FileBackend(Context context) {
this.context = context;
+
+ int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
+ int cacheSize = maxMemory / 8;
+ thumbnailCache = new LruCache(cacheSize) {
+ @Override
+ protected int sizeOf(String key, Bitmap bitmap) {
+ return bitmap.getByteCount() / 1024;
+ }
+ };
+
}
-
- private File getImageFile(Message message) {
+
+ public File getImageFile(Message message) {
Conversation conversation = message.getConversation();
- String prefix = context.getFilesDir().getAbsolutePath();
- String path = prefix+"/"+conversation.getAccount().getJid()+"/"+conversation.getContactJid();
+ String prefix = context.getFilesDir().getAbsolutePath();
+ String path = prefix + "/" + conversation.getAccount().getJid() + "/"
+ + conversation.getContactJid();
String filename = message.getUuid() + ".webp";
- return new File(path+"/"+filename);
+ return new File(path + "/" + filename);
}
+ private Bitmap resize(Bitmap originalBitmap, int size) {
+ int w = originalBitmap.getWidth();
+ int h = originalBitmap.getHeight();
+ if (Math.max(w, h) > size) {
+ int scalledW;
+ int scalledH;
+ if (w <= h) {
+ scalledW = (int) (w / ((double) h / size));
+ scalledH = size;
+ } else {
+ scalledW = size;
+ scalledH = (int) (h / ((double) w / size));
+ }
+ Bitmap scalledBitmap = Bitmap.createScaledBitmap(
+ originalBitmap, scalledW, scalledH, true);
+ return scalledBitmap;
+ } else {
+ return originalBitmap;
+ }
+ }
+
public File copyImageToPrivateStorage(Message message, Uri image) {
try {
- InputStream is = context.getContentResolver().openInputStream(image);
+ InputStream is = context.getContentResolver()
+ .openInputStream(image);
File file = getImageFile(message);
file.getParentFile().mkdirs();
file.createNewFile();
OutputStream os = new FileOutputStream(file);
Bitmap originalBitmap = BitmapFactory.decodeStream(is);
is.close();
- int w = originalBitmap.getWidth();
- int h = originalBitmap.getHeight();
- boolean success;
- if (Math.max(w, h) > IMAGE_SIZE) {
- int scalledW;
- int scalledH;
- if (w<=h) {
- scalledW = (int) (w / ((double) h/IMAGE_SIZE));
- scalledH = IMAGE_SIZE;
- } else {
- scalledW = IMAGE_SIZE;
- scalledH = (int) (h / ((double) w/IMAGE_SIZE));
- }
- Bitmap scalledBitmap = Bitmap.createScaledBitmap(originalBitmap, scalledW,scalledH, true);
- success = scalledBitmap.compress(Bitmap.CompressFormat.WEBP, 75, os);
- } else {
- success = originalBitmap.compress(Bitmap.CompressFormat.WEBP, 75, os);
- }
+ Bitmap scalledBitmap = resize(originalBitmap, IMAGE_SIZE);
+ boolean success = scalledBitmap.compress(Bitmap.CompressFormat.WEBP,75,os);
if (!success) {
- Log.d("xmppService","couldnt compress");
+ Log.d("xmppService", "couldnt compress");
}
os.close();
return file;
@@ -75,12 +92,24 @@ public class FileBackend {
// TODO Auto-generated catch block
e.printStackTrace();
}
-
+
return null;
}
-
-
+
public Bitmap getImageFromMessage(Message message) {
- return BitmapFactory.decodeFile(getImageFile(message).getAbsolutePath());
+ return BitmapFactory
+ .decodeFile(getImageFile(message).getAbsolutePath());
+ }
+
+ public Bitmap getThumbnailFromMessage(Message message, int size) {
+ Bitmap thumbnail = thumbnailCache.get(message.getUuid());
+ if (thumbnail==null) {
+ Log.d("xmppService","creating new thumbnail" + message.getUuid());
+ Bitmap fullsize = BitmapFactory.decodeFile(getImageFile(message)
+ .getAbsolutePath());
+ thumbnail = resize(fullsize, size);
+ this.thumbnailCache.put(message.getUuid(), thumbnail);
+ }
+ return thumbnail;
}
}
diff --git a/src/eu/siacs/conversations/services/JingleConnectionManager.java b/src/eu/siacs/conversations/services/JingleConnectionManager.java
new file mode 100644
index 000000000..f2ca927a5
--- /dev/null
+++ b/src/eu/siacs/conversations/services/JingleConnectionManager.java
@@ -0,0 +1,50 @@
+package eu.siacs.conversations.services;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xmpp.JingleConnection;
+import eu.siacs.conversations.xmpp.stanzas.jingle.JinglePacket;
+
+public class JingleConnectionManager {
+
+ private XmppConnectionService xmppConnectionService;
+
+ private ConcurrentHashMap connections = new ConcurrentHashMap();
+
+ public JingleConnectionManager(XmppConnectionService service) {
+ this.xmppConnectionService = service;
+ }
+
+ public void deliverPacket(Account account, JinglePacket packet) {
+ String id = generateInternalId(account.getJid(), packet.getFrom(), packet.getSessionId());
+ }
+
+ public JingleConnection createNewConnection(Message message) {
+ Account account = message.getConversation().getAccount();
+ JingleConnection connection = new JingleConnection(this,account, message.getCounterpart());
+ String id = generateInternalId(account.getJid(), message.getCounterpart(), connection.getSessionId());
+ connection.init(message);
+ return connection;
+ }
+
+ private String generateInternalId(String account, String counterpart, String sid) {
+ return account+"#"+counterpart+"#"+sid;
+
+ }
+
+ public XmppConnectionService getXmppConnectionService() {
+ return this.xmppConnectionService;
+ }
+
+ public Element getPrimaryCanditate(String jid) {
+ Element canditate = new Element("canditate");
+ canditate.setAttribute("cid","122");
+ canditate.setAttribute("port","1234");
+ canditate.setAttribute("jid", jid);
+ canditate.setAttribute("type", "assisted");
+ return canditate;
+ }
+}
diff --git a/src/eu/siacs/conversations/services/XmppConnectionService.java b/src/eu/siacs/conversations/services/XmppConnectionService.java
index e8ec6f7f1..62e487733 100644
--- a/src/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/eu/siacs/conversations/services/XmppConnectionService.java
@@ -87,7 +87,8 @@ public class XmppConnectionService extends Service {
private List accounts;
private List conversations = null;
-
+ private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager(this);
+
public OnConversationListChangedListener convChangedListener = null;
private int convChangedListenerCount = 0;
private OnAccountListChangedListener accountChangedListener = null;
@@ -389,13 +390,15 @@ public class XmppConnectionService extends Service {
return this.fileBackend;
}
- public void attachImageToConversation(Conversation conversation, Uri uri) {
+ public Message attachImageToConversation(Conversation conversation, Uri uri) {
Message message = new Message(conversation, "", Message.ENCRYPTION_NONE);
message.setType(Message.TYPE_IMAGE);
File file = this.fileBackend.copyImageToPrivateStorage(message, uri);
Log.d(LOGTAG,"new file"+file.getAbsolutePath());
conversation.getMessages().add(message);
databaseBackend.createMessage(message);
+ sendMessage(message, null);
+ return message;
}
@@ -655,48 +658,52 @@ public class XmppConnectionService extends Service {
boolean saveInDb = false;
boolean addToConversation = false;
if (account.getStatus() == Account.STATUS_ONLINE) {
- MessagePacket packet;
- if (message.getEncryption() == Message.ENCRYPTION_OTR) {
- if (!conv.hasValidOtrSession()) {
- // starting otr session. messages will be send later
- conv.startOtrSession(getApplicationContext(), presence,true);
- } else if (conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
- // otr session aleary exists, creating message packet
- // accordingly
- packet = prepareMessagePacket(account, message,
- conv.getOtrSession());
- account.getXmppConnection().sendMessagePacket(packet);
- message.setStatus(Message.STATUS_SEND);
- }
- saveInDb = true;
- addToConversation = true;
- } else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
- message.getConversation().endOtrIfNeeded();
- long keyId = message.getConversation().getContact()
- .getPgpKeyId();
- packet = new MessagePacket();
- packet.setType(MessagePacket.TYPE_CHAT);
- packet.setFrom(message.getConversation().getAccount()
- .getFullJid());
- packet.setTo(message.getCounterpart());
- packet.setBody("This is an XEP-0027 encryted message");
- packet.addChild("x", "jabber:x:encrypted").setContent(message.getEncryptedBody());
- account.getXmppConnection().sendMessagePacket(packet);
- message.setStatus(Message.STATUS_SEND);
- message.setEncryption(Message.ENCRYPTION_DECRYPTED);
- saveInDb = true;
- addToConversation = true;
+ if (message.getType() == Message.TYPE_IMAGE) {
+ mJingleConnectionManager.createNewConnection(message);
} else {
- message.getConversation().endOtrIfNeeded();
- // don't encrypt
- if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
- message.setStatus(Message.STATUS_SEND);
+ MessagePacket packet;
+ if (message.getEncryption() == Message.ENCRYPTION_OTR) {
+ if (!conv.hasValidOtrSession()) {
+ // starting otr session. messages will be send later
+ conv.startOtrSession(getApplicationContext(), presence,true);
+ } else if (conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
+ // otr session aleary exists, creating message packet
+ // accordingly
+ packet = prepareMessagePacket(account, message,
+ conv.getOtrSession());
+ account.getXmppConnection().sendMessagePacket(packet);
+ message.setStatus(Message.STATUS_SEND);
+ }
saveInDb = true;
addToConversation = true;
+ } else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
+ message.getConversation().endOtrIfNeeded();
+ long keyId = message.getConversation().getContact()
+ .getPgpKeyId();
+ packet = new MessagePacket();
+ packet.setType(MessagePacket.TYPE_CHAT);
+ packet.setFrom(message.getConversation().getAccount()
+ .getFullJid());
+ packet.setTo(message.getCounterpart());
+ packet.setBody("This is an XEP-0027 encryted message");
+ packet.addChild("x", "jabber:x:encrypted").setContent(message.getEncryptedBody());
+ account.getXmppConnection().sendMessagePacket(packet);
+ message.setStatus(Message.STATUS_SEND);
+ message.setEncryption(Message.ENCRYPTION_DECRYPTED);
+ saveInDb = true;
+ addToConversation = true;
+ } else {
+ message.getConversation().endOtrIfNeeded();
+ // don't encrypt
+ if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
+ message.setStatus(Message.STATUS_SEND);
+ saveInDb = true;
+ addToConversation = true;
+ }
+
+ packet = prepareMessagePacket(account, message, null);
+ account.getXmppConnection().sendMessagePacket(packet);
}
-
- packet = prepareMessagePacket(account, message, null);
- account.getXmppConnection().sendMessagePacket(packet);
}
} else {
// account is offline
diff --git a/src/eu/siacs/conversations/ui/ConversationFragment.java b/src/eu/siacs/conversations/ui/ConversationFragment.java
index a9bde53d0..ab0259e1b 100644
--- a/src/eu/siacs/conversations/ui/ConversationFragment.java
+++ b/src/eu/siacs/conversations/ui/ConversationFragment.java
@@ -33,6 +33,7 @@ import android.graphics.Typeface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -148,6 +149,8 @@ public class ConversationFragment extends Fragment {
public View onCreateView(final LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
+ final DisplayMetrics metrics = getResources().getDisplayMetrics();
+
this.inflater = inflater;
final View view = inflater.inflate(R.layout.fragment_conversation,
@@ -264,7 +267,7 @@ public class ConversationFragment extends Fragment {
}
if (item.getType() == Message.TYPE_IMAGE) {
viewHolder.image.setVisibility(View.VISIBLE);
- viewHolder.image.setImageBitmap(activity.xmppConnectionService.getFileBackend().getImageFromMessage(item));
+ viewHolder.image.setImageBitmap(activity.xmppConnectionService.getFileBackend().getThumbnailFromMessage(item,(int) (metrics.density * 288)));
viewHolder.messageBody.setVisibility(View.GONE);
} else {
if (viewHolder.image != null) viewHolder.image.setVisibility(View.GONE);
diff --git a/src/eu/siacs/conversations/xml/Element.java b/src/eu/siacs/conversations/xml/Element.java
index 2f1d7ad8c..ce1d10ce1 100644
--- a/src/eu/siacs/conversations/xml/Element.java
+++ b/src/eu/siacs/conversations/xml/Element.java
@@ -139,4 +139,8 @@ public class Element {
content = content.replace("'","'");
return content;
}
+
+ public void clearChildren() {
+ this.children.clear();
+ }
}
diff --git a/src/eu/siacs/conversations/xmpp/JingleConnection.java b/src/eu/siacs/conversations/xmpp/JingleConnection.java
new file mode 100644
index 000000000..34587a934
--- /dev/null
+++ b/src/eu/siacs/conversations/xmpp/JingleConnection.java
@@ -0,0 +1,67 @@
+package eu.siacs.conversations.xmpp;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
+
+import android.util.Log;
+
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.services.JingleConnectionManager;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xmpp.stanzas.jingle.Content;
+import eu.siacs.conversations.xmpp.stanzas.jingle.JinglePacket;
+
+public class JingleConnection {
+
+ private JingleConnectionManager mJingleConnectionManager;
+ private XmppConnectionService mXmppConnectionService;
+
+ private String sessionId;
+ private Account account;
+ private String counterpart;
+ private List canditates = new ArrayList();
+
+ public JingleConnection(JingleConnectionManager mJingleConnectionManager, Account account, String counterpart) {
+ this.mJingleConnectionManager = mJingleConnectionManager;
+ this.mXmppConnectionService = mJingleConnectionManager.getXmppConnectionService();
+ this.account = account;
+ this.counterpart = counterpart;
+ SecureRandom random = new SecureRandom();
+ sessionId = new BigInteger(100, random).toString(32);
+ this.canditates.add(this.mJingleConnectionManager.getPrimaryCanditate(account.getJid()));
+ }
+
+ public String getSessionId() {
+ return this.sessionId;
+ }
+
+ public void init(Message message) {
+ JinglePacket packet = this.bootstrapPacket();
+ packet.setAction("session-initiate");
+ packet.setInitiator(this.account.getFullJid());
+ Content content = new Content();
+ if (message.getType() == Message.TYPE_IMAGE) {
+ //creator='initiator' name='a-file-offer'
+ content.setAttribute("creator", "initiator");
+ content.setAttribute("name", "a-file-offer");
+ content.offerFile(this.mXmppConnectionService.getFileBackend().getImageFile(message));
+ content.setCanditates(this.canditates);
+ packet.setContent(content);
+ Log.d("xmppService",packet.toString());
+ account.getXmppConnection().sendIqPacket(packet, null);
+ }
+ }
+
+ private JinglePacket bootstrapPacket() {
+ JinglePacket packet = new JinglePacket();
+ packet.setFrom(account.getFullJid());
+ packet.setTo(this.counterpart+"/Gajim");
+ packet.setSessionId(this.sessionId);
+ return packet;
+ }
+
+}
diff --git a/src/eu/siacs/conversations/xmpp/stanzas/jingle/Content.java b/src/eu/siacs/conversations/xmpp/stanzas/jingle/Content.java
index ebd212b86..ed51a2a5c 100644
--- a/src/eu/siacs/conversations/xmpp/stanzas/jingle/Content.java
+++ b/src/eu/siacs/conversations/xmpp/stanzas/jingle/Content.java
@@ -1,5 +1,8 @@
package eu.siacs.conversations.xmpp.stanzas.jingle;
+import java.io.File;
+import java.util.List;
+
import eu.siacs.conversations.xml.Element;
public class Content extends Element {
@@ -10,4 +13,23 @@ public class Content extends Element {
public Content() {
super("content");
}
+
+ public void offerFile(File actualFile) {
+ Element description = this.addChild("description", "urn:xmpp:jingle:apps:file-transfer:3");
+ Element offer = description.addChild("offer");
+ Element file = offer.addChild("file");
+ file.addChild("size").setContent(""+actualFile.length());
+ file.addChild("name").setContent(actualFile.getName());
+ }
+
+ public void setCanditates(List canditates) {
+ Element transport = this.findChild("transport", "urn:xmpp:jingle:transports:s5b:1");
+ if (transport==null) {
+ transport = this.addChild("transport", "urn:xmpp:jingle:transports:s5b:1");
+ }
+ transport.clearChildren();
+ for(Element canditate : canditates) {
+ transport.addChild(canditate);
+ }
+ }
}
diff --git a/src/eu/siacs/conversations/xmpp/stanzas/jingle/JinglePacket.java b/src/eu/siacs/conversations/xmpp/stanzas/jingle/JinglePacket.java
index 51c60d1fb..4c444a74c 100644
--- a/src/eu/siacs/conversations/xmpp/stanzas/jingle/JinglePacket.java
+++ b/src/eu/siacs/conversations/xmpp/stanzas/jingle/JinglePacket.java
@@ -6,6 +6,7 @@ import eu.siacs.conversations.xmpp.stanzas.IqPacket;
public class JinglePacket extends IqPacket {
Content content = null;
Reason reason = null;
+ Element jingle = new Element("jingle");
@Override
public Element addChild(Element child) {
@@ -22,27 +23,25 @@ public class JinglePacket extends IqPacket {
this.reason.setChildren(reasonElement.getChildren());
this.reason.setAttributes(reasonElement.getAttributes());
}
- this.build();
- this.findChild("jingle").setAttributes(child.getAttributes());
+ this.jingle.setAttributes(child.getAttributes());
}
return child;
}
public JinglePacket setContent(Content content) {
this.content = content;
- this.build();
return this;
}
public JinglePacket setReason(Reason reason) {
this.reason = reason;
- this.build();
return this;
}
private void build() {
this.children.clear();
- Element jingle = addChild("jingle", "urn:xmpp:jingle:1");
+ this.jingle.clearChildren();
+ this.jingle.setAttribute("xmlns", "urn:xmpp:jingle:1");
if (this.content!=null) {
jingle.addChild(this.content);
}
@@ -50,5 +49,28 @@ public class JinglePacket extends IqPacket {
jingle.addChild(this.reason);
}
this.children.add(jingle);
+ this.setAttribute("type", "set");
+ }
+
+ public String getSessionId() {
+ return this.jingle.getAttribute("sid");
+ }
+
+ public void setSessionId(String sid) {
+ this.jingle.setAttribute("sid", sid);
+ }
+
+ @Override
+ public String toString() {
+ this.build();
+ return super.toString();
+ }
+
+ public void setAction(String action) {
+ this.jingle.setAttribute("action", action);
+ }
+
+ public void setInitiator(String initiator) {
+ this.jingle.setAttribute("initiator", initiator);
}
}