jingle connection and manager. able to trigger dialog in gajim

Daniel Gultsch created

Change summary

res/layout/message_recieved.xml                                  |  3 
res/layout/message_sent.xml                                      |  3 
src/eu/siacs/conversations/persistance/FileBackend.java          | 97 +
src/eu/siacs/conversations/services/JingleConnectionManager.java | 50 +
src/eu/siacs/conversations/services/XmppConnectionService.java   | 85 
src/eu/siacs/conversations/ui/ConversationFragment.java          |  5 
src/eu/siacs/conversations/xml/Element.java                      |  4 
src/eu/siacs/conversations/xmpp/JingleConnection.java            | 67 +
src/eu/siacs/conversations/xmpp/stanzas/jingle/Content.java      | 22 
src/eu/siacs/conversations/xmpp/stanzas/jingle/JinglePacket.java | 32 
10 files changed, 287 insertions(+), 81 deletions(-)

Detailed changes

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"
                 />
             
             <TextView

res/layout/message_sent.xml 🔗

@@ -25,7 +25,8 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:adjustViewBounds="true"
-                android:maxHeight="300dp"
+                android:maxHeight="288dp"
+                android:maxWidth="288dp"
                 />
 
             <TextView

src/eu/siacs/conversations/persistance/FileBackend.java 🔗

@@ -6,65 +6,82 @@ import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.math.BigInteger;
 
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.net.Uri;
 import android.util.Log;
+import android.util.LruCache;
 
 import eu.siacs.conversations.entities.Conversation;
 import eu.siacs.conversations.entities.Message;
 
-
 public class FileBackend {
-	
+
 	private static int IMAGE_SIZE = 1920;
-	
+
 	private Context context;
-	
+	private LruCache<String, Bitmap> thumbnailCache;
+
 	public FileBackend(Context context) {
 		this.context = context;
+		
+		int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
+		int cacheSize = maxMemory / 8;
+		thumbnailCache = new LruCache<String, Bitmap>(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;
 	}
 }

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<String, JingleConnection> connections = new ConcurrentHashMap<String, JingleConnection>();
+	
+	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;
+	}
+}

src/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -87,7 +87,8 @@ public class XmppConnectionService extends Service {
 
 	private List<Account> accounts;
 	private List<Conversation> 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) {
+				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

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

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<Element> canditates = new ArrayList<Element>();
+	
+	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;
+	}
+
+}

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

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