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