some code cleanup. added setting to auto accept files. socks5 connections are now threaded

Daniel Gultsch created

Change summary

res/values/arrays.xml                                               |  12 
res/xml/preferences.xml                                             |   7 
src/eu/siacs/conversations/persistance/FileBackend.java             |   8 
src/eu/siacs/conversations/services/XmppConnectionService.java      |  14 
src/eu/siacs/conversations/utils/UIHelper.java                      |   2 
src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java        | 164 
src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java |   4 
src/eu/siacs/conversations/xmpp/jingle/OnSocksConnection.java       |   6 
src/eu/siacs/conversations/xmpp/jingle/SocksConnection.java         |  73 
src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java         |  22 
10 files changed, 234 insertions(+), 78 deletions(-)

Detailed changes

res/values/arrays.xml 🔗

@@ -7,4 +7,16 @@
         <item>Conversations</item>
         <item>Android</item>
     </array>
+    <string-array name="filesizes">
+        <item>never</item>
+        <item>256 KB</item>
+        <item>512 KB</item>
+        <item>1 MB</item>
+    </string-array>
+    <string-array name="filesizes_values">
+        <item>0</item>
+        <item>262144</item>
+        <item>524288</item>
+        <item>1048576</item>
+    </string-array>
 </resources>

res/xml/preferences.xml 🔗

@@ -15,6 +15,13 @@
             android:entries="@array/resources"
             android:entryValues="@array/resources"
             android:defaultValue="Mobile"/>
+        <ListPreference 
+            android:key="auto_accept_file_size"
+            android:title="Accept files"
+            android:summary="Automatically accept files smaller than"
+            android:entries="@array/filesizes"
+            android:entryValues="@array/filesizes_values"
+            android:defaultValue="524288"/>
     </PreferenceCategory>
     <PreferenceCategory 
         android:title="Notification Settings">

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

@@ -38,7 +38,7 @@ public class FileBackend {
 
 	}
 
-	public JingleFile getImageFile(Message message) {
+	public JingleFile getJingleFile(Message message) {
 		Conversation conversation = message.getConversation();
 		String prefix = context.getFilesDir().getAbsolutePath();
 		String path = prefix + "/" + conversation.getAccount().getJid() + "/"
@@ -72,7 +72,7 @@ public class FileBackend {
 		try {
 			InputStream is = context.getContentResolver()
 					.openInputStream(image);
-			JingleFile file = getImageFile(message);
+			JingleFile file = getJingleFile(message);
 			file.getParentFile().mkdirs();
 			file.createNewFile();
 			OutputStream os = new FileOutputStream(file);
@@ -98,14 +98,14 @@ public class FileBackend {
 
 	public Bitmap getImageFromMessage(Message message) {
 		return BitmapFactory
-				.decodeFile(getImageFile(message).getAbsolutePath());
+				.decodeFile(getJingleFile(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)
+			Bitmap fullsize = BitmapFactory.decodeFile(getJingleFile(message)
 					.getAbsolutePath());
 			thumbnail = resize(fullsize, size);
 			this.thumbnailCache.put(message.getUuid(), thumbnail);

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

@@ -124,9 +124,7 @@ public class XmppConnectionService extends Service {
 				MessagePacket packet) {
 			Message message = null;
 			boolean notify = true;
-			if(PreferenceManager
-					.getDefaultSharedPreferences(getApplicationContext())
-					.getBoolean("notification_grace_period_after_carbon_received", true)){
+			if(getPreferences().getBoolean("notification_grace_period_after_carbon_received", true)){
 				notify=(SystemClock.elapsedRealtime() - lastCarbonMessageReceived) > CARBON_GRACE_PERIOD;
 			}
 
@@ -625,8 +623,7 @@ public class XmppConnectionService extends Service {
 	}
 
 	public XmppConnection createConnection(Account account) {
-		SharedPreferences sharedPref = PreferenceManager
-				.getDefaultSharedPreferences(getApplicationContext());
+		SharedPreferences sharedPref = getPreferences();
 		account.setResource(sharedPref.getString("resource", "mobile").toLowerCase(Locale.getDefault()));
 		XmppConnection connection = new XmppConnection(account, this.pm);
 		connection.setOnMessagePacketReceivedListener(this.messageListener);
@@ -1204,8 +1201,7 @@ public class XmppConnectionService extends Service {
 	}
 
 	public void createContact(Contact contact) {
-		SharedPreferences sharedPref = PreferenceManager
-				.getDefaultSharedPreferences(getApplicationContext());
+		SharedPreferences sharedPref = getPreferences();
 		boolean autoGrant = sharedPref.getBoolean("grant_new_contacts", true);
 		if (autoGrant) {
 			contact.setSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT);
@@ -1396,4 +1392,8 @@ public class XmppConnectionService extends Service {
 			convChangedListener.onConversationListChanged();
 		}
 	}
+	
+	public SharedPreferences getPreferences() {
+		return PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+	}
 }

src/eu/siacs/conversations/utils/UIHelper.java 🔗

@@ -419,7 +419,7 @@ public class UIHelper {
 			mBuilder.setContentText(names.toString());
 			mBuilder.setStyle(style);
 		}
-		if (currentCon!=null) {
+		if ((currentCon!=null)&&(notify)) {
 			targetUuid=currentCon.getUuid();
 		}
 		if (unread.size() != 0) {

src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java 🔗

@@ -4,7 +4,6 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.Map.Entry;
 
 import android.util.Log;
@@ -39,7 +38,9 @@ public class JingleConnection {
 	private String initiator;
 	private String responder;
 	private List<Element> candidates = new ArrayList<Element>();
+	private List<String> candidatesUsedByCounterpart = new ArrayList<String>();
 	private HashMap<String, SocksConnection> connections = new HashMap<String, SocksConnection>();
+	private Content content = new Content();
 	private JingleFile file = null;
 	
 	private OnIqPacketReceived responseListener = new OnIqPacketReceived() {
@@ -100,9 +101,9 @@ public class JingleConnection {
 			this.mJingleConnectionManager.getPrimaryCandidate(account, new OnPrimaryCandidateFound() {
 				
 				@Override
-				public void onPrimaryCandidateFound(boolean success, Element canditate) {
+				public void onPrimaryCandidateFound(boolean success, Element candidate) {
 					if (success) {
-						candidates.add(canditate);
+						mergeCandidate(candidate);
 					}
 					sendInitRequest();
 				}
@@ -116,24 +117,40 @@ public class JingleConnection {
 		this.message = new Message(conversation, "receiving image file", Message.ENCRYPTION_NONE);
 		this.message.setType(Message.TYPE_IMAGE);
 		this.message.setStatus(Message.STATUS_RECIEVING);
+		String[] fromParts = packet.getFrom().split("/");
+		this.message.setPresence(fromParts[1]);
 		this.account = account;
 		this.initiator = packet.getFrom();
 		this.responder = this.account.getFullJid();
 		this.sessionId = packet.getSessionId();
-		this.candidates.addAll(packet.getJingleContent().getCanditates());
-		Log.d("xmppService","new incomming jingle session "+this.sessionId+" num canditaes:"+this.candidates.size());
+		this.content = packet.getJingleContent();
+		this.mergeCandidates(this.content.getCanditates());
+		Element fileOffer = packet.getJingleContent().getFileOffer();
+		if (fileOffer!=null) {
+			this.file = this.mXmppConnectionService.getFileBackend().getJingleFile(message);
+			Element fileSize = fileOffer.findChild("size");
+			Element fileName = fileOffer.findChild("name");
+			this.file.setExpectedSize(Long.parseLong(fileSize.getContent()));
+			if (this.file.getExpectedSize()>=this.mJingleConnectionManager.getAutoAcceptFileSize()) {
+				Log.d("xmppService","auto accepting file from "+packet.getFrom());
+				this.sendAccept();
+			} else {
+				Log.d("xmppService","not auto accepting new file offer with size: "+this.file.getExpectedSize()+" allowed size:"+this.mJingleConnectionManager.getAutoAcceptFileSize());
+			}
+		} else {
+			Log.d("xmppService","no file offer was attached. aborting");
+		}
 	}
 	
 	private void sendInitRequest() {
 		JinglePacket packet = this.bootstrapPacket();
 		packet.setAction("session-initiate");
-		packet.setInitiator(this.account.getFullJid());
-		Content content = new Content();
+		this.content = new Content();
 		if (message.getType() == Message.TYPE_IMAGE) {
 			content.setAttribute("creator", "initiator");
 			content.setAttribute("name", "a-file-offer");
-			this.file = this.mXmppConnectionService.getFileBackend().getImageFile(message);
-			content.offerFile(file,message.getBody());
+			this.file = this.mXmppConnectionService.getFileBackend().getJingleFile(message);
+			content.setFileOffer(this.file);
 			content.setCandidates(this.mJingleConnectionManager.nextRandomId(),this.candidates);
 			packet.setContent(content);
 			Log.d("xmppService",packet.toString());
@@ -142,58 +159,103 @@ public class JingleConnection {
 		}
 	}
 	
+	private void sendAccept() {
+		this.mJingleConnectionManager.getPrimaryCandidate(this.account, new OnPrimaryCandidateFound() {
+			
+			@Override
+			public void onPrimaryCandidateFound(boolean success, Element candidate) {
+				if (success) {
+					if (mergeCandidate(candidate)) {
+						content.addCandidate(candidate);
+					}
+				}
+				JinglePacket packet = bootstrapPacket();
+				packet.setAction("session-accept");
+				packet.setContent(content);
+				Log.d("xmppService","sending session accept: "+packet.toString());
+				account.getXmppConnection().sendIqPacket(packet, new OnIqPacketReceived() {
+					
+					@Override
+					public void onIqPacketReceived(Account account, IqPacket packet) {
+						if (packet.getType() != IqPacket.TYPE_ERROR) {
+							Log.d("xmppService","opsing side has acked our session-accept");
+							connectWithCandidates();
+						}
+					}
+				});
+			}
+		});
+		
+	}
+	
 	private JinglePacket bootstrapPacket() {
 		JinglePacket packet = new JinglePacket();
 		packet.setFrom(account.getFullJid());
 		packet.setTo(this.message.getCounterpart()); //fixme, not right in all cases;
 		packet.setSessionId(this.sessionId);
+		packet.setInitiator(this.initiator);
 		return packet;
 	}
 	
 	private void accept(JinglePacket packet) {
 		Log.d("xmppService","session-accept: "+packet.toString());
 		Content content = packet.getJingleContent();
-		this.candidates.addAll(content.getCanditates());
+		this.mergeCandidates(content.getCanditates());
 		this.status = STATUS_ACCEPTED;
 		this.connectWithCandidates();
 		IqPacket response = packet.generateRespone(IqPacket.TYPE_RESULT);
-		Log.d("xmppService","response "+response.toString());
 		account.getXmppConnection().sendIqPacket(response, null);
 	}
-	
+
 	private void transportInfo(JinglePacket packet) {
 		Content content = packet.getJingleContent();
 		Log.d("xmppService","transport info : "+content.toString());
 		String cid = content.getUsedCandidate();
 		if (cid!=null) {
-			final JingleFile file = this.mXmppConnectionService.getFileBackend().getImageFile(this.message);
-			final SocksConnection connection = this.connections.get(cid);
-			final OnFileTransmitted callback = new OnFileTransmitted() {
+			Log.d("xmppService","candidate used by counterpart:"+cid);
+			this.candidatesUsedByCounterpart.add(cid);
+			if (this.connections.containsKey(cid)) {
+				this.connect(this.connections.get(cid));
+			}
+		}
+		IqPacket response = packet.generateRespone(IqPacket.TYPE_RESULT);
+		account.getXmppConnection().sendIqPacket(response, null);
+	}
+
+	private void connect(final SocksConnection connection) {
+		final OnFileTransmitted callback = new OnFileTransmitted() {
+			
+			@Override
+			public void onFileTransmitted(JingleFile file) {
+				Log.d("xmppService","sucessfully transmitted file. sha1:"+file.getSha1Sum());
+			}
+		};
+		if (connection.isProxy()) {
+			IqPacket activation = new IqPacket(IqPacket.TYPE_SET);
+			activation.setTo(connection.getJid());
+			activation.query("http://jabber.org/protocol/bytestreams").setAttribute("sid", this.getSessionId());
+			activation.query().addChild("activate").setContent(this.getResponder());
+			Log.d("xmppService","connection is proxy. need to activate "+activation.toString());
+			this.account.getXmppConnection().sendIqPacket(activation, new OnIqPacketReceived() {
 				
 				@Override
-				public void onFileTransmitted(JingleFile file) {
-					Log.d("xmppService","sucessfully transmitted file. sha1:"+file.getSha1Sum());
-				}
-			};
-			final IqPacket response = packet.generateRespone(IqPacket.TYPE_RESULT);
-			if (connection.isProxy()) {
-				IqPacket activation = new IqPacket(IqPacket.TYPE_SET);
-				activation.setTo(connection.getJid());
-				activation.query("http://jabber.org/protocol/bytestreams").setAttribute("sid", this.getSessionId());
-				activation.query().addChild("activate").setContent(this.getResponder());
-				Log.d("xmppService","connection is proxy. need to activate "+activation.toString());
-				this.account.getXmppConnection().sendIqPacket(activation, new OnIqPacketReceived() {
-					
-					@Override
-					public void onIqPacketReceived(Account account, IqPacket packet) {
-						account.getXmppConnection().sendIqPacket(response, null);
-						Log.d("xmppService","activation result: "+packet.toString());
+				public void onIqPacketReceived(Account account, IqPacket packet) {
+					Log.d("xmppService","activation result: "+packet.toString());
+					if (initiator.equals(account.getFullJid())) {
+						Log.d("xmppService","we were initiating. sending file");
 						connection.send(file,callback);
+					} else {
+						Log.d("xmppService","we were responding. receiving file");
 					}
-				});
-			} else {
-				account.getXmppConnection().sendIqPacket(response, null);
+					
+				}
+			});
+		} else {
+			if (initiator.equals(account.getFullJid())) {
+				Log.d("xmppService","we were initiating. sending file");
 				connection.send(file,callback);
+			} else {
+				Log.d("xmppService","we were responding. receiving file");
 			}
 		}
 	}
@@ -212,13 +274,25 @@ public class JingleConnection {
 	
 	private void connectWithCandidates() {
 		for(Element canditate : this.candidates) {
+			
 			String host = canditate.getAttribute("host");
 			int port = Integer.parseInt(canditate.getAttribute("port"));
 			String type = canditate.getAttribute("type");
 			String jid = canditate.getAttribute("jid");
 			SocksConnection socksConnection = new SocksConnection(this, host, jid, port,type);
-			socksConnection.connect();
-			this.connections.put(canditate.getAttribute("cid"), socksConnection);
+			connections.put(canditate.getAttribute("cid"), socksConnection);
+			socksConnection.connect(new OnSocksConnection() {
+				
+				@Override
+				public void failed() {
+					Log.d("xmppService","socks5 failed");
+				}
+				
+				@Override
+				public void established() {
+					Log.d("xmppService","established socks5");
+				}
+			});
 		}
 	}
 	
@@ -246,4 +320,20 @@ public class JingleConnection {
 	public int getStatus() {
 		return this.status;
 	}
+	
+	private boolean mergeCandidate(Element candidate) {
+		for(Element c : this.candidates) {
+			if (c.getAttribute("host").equals(candidate.getAttribute("host"))&&(c.getAttribute("port").equals(candidate.getAttribute("port")))) {
+				return false;
+			}
+		}
+		this.candidates.add(candidate);
+		return true;
+	}
+	
+	private void mergeCandidates(List<Element> canditates) {
+		for(Element c : canditates) {
+			this.mergeCandidate(c);
+		}
+	}
 }

src/eu/siacs/conversations/xmpp/jingle/SocksConnection.java 🔗

@@ -25,6 +25,7 @@ public class SocksConnection {
 	private boolean isProxy = false;
 	private String destination;
 	private OutputStream outputStream;
+	private boolean isEstablished = false;
 
 	public SocksConnection(JingleConnection jingleConnection, String host,
 			String jid, int port, String type) {
@@ -42,40 +43,52 @@ public class SocksConnection {
 			mDigest.reset();
 			this.destination = CryptoHelper.bytesToHex(mDigest
 					.digest(destBuilder.toString().getBytes()));
-			Log.d("xmppService", "host=" + host + ", port=" + port
-					+ ", destination: " + destination);
 		} catch (NoSuchAlgorithmException e) {
 
 		}
 	}
 
-	public boolean connect() {
-		try {
-			this.socket = new Socket(this.host, this.port);
-			InputStream is = socket.getInputStream();
-			this.outputStream = socket.getOutputStream();
-			byte[] login = { 0x05, 0x01, 0x00 };
-			byte[] expectedReply = { 0x05, 0x00 };
-			byte[] reply = new byte[2];
-			this.outputStream.write(login);
-			is.read(reply);
-			if (Arrays.equals(reply, expectedReply)) {
-				String connect = "" + '\u0005' + '\u0001' + '\u0000' + '\u0003'
-						+ '\u0028' + this.destination + '\u0000' + '\u0000';
-				this.outputStream.write(connect.getBytes());
-				byte[] result = new byte[2];
-				is.read(result);
-				int status = result[0];
-				return (status == 0);
-			} else {
-				socket.close();
-				return false;
+	public void connect(final OnSocksConnection callback) {
+		new Thread(new Runnable() {
+			
+			@Override
+			public void run() {
+				try {
+					socket = new Socket(host, port);
+					InputStream is = socket.getInputStream();
+					outputStream = socket.getOutputStream();
+					byte[] login = { 0x05, 0x01, 0x00 };
+					byte[] expectedReply = { 0x05, 0x00 };
+					byte[] reply = new byte[2];
+					outputStream.write(login);
+					is.read(reply);
+					if (Arrays.equals(reply, expectedReply)) {
+						String connect = "" + '\u0005' + '\u0001' + '\u0000' + '\u0003'
+								+ '\u0028' + destination + '\u0000' + '\u0000';
+						outputStream.write(connect.getBytes());
+						byte[] result = new byte[2];
+						is.read(result);
+						int status = result[1];
+						if (status == 0) {
+							Log.d("xmppService", "established connection with "+host + ":" + port
+									+ "/" + destination);
+							isEstablished = true;
+							callback.established();
+						} else {
+							callback.failed();
+						}
+					} else {
+						socket.close();
+						callback.failed();
+					}
+				} catch (UnknownHostException e) {
+					callback.failed();
+				} catch (IOException e) {
+					callback.failed();
+				}
 			}
-		} catch (UnknownHostException e) {
-			return false;
-		} catch (IOException e) {
-			return false;
-		}
+		}).start();
+		
 	}
 
 	public void send(final JingleFile file, final OnFileTransmitted callback) {
@@ -141,4 +154,8 @@ public class SocksConnection {
 			}
 		}
 	}
+	
+	public boolean isEstablished() {
+		return this.isEstablished;
+	}
 }

src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java 🔗

@@ -15,13 +15,25 @@ public class Content extends Element {
 		super("content");
 	}
 
-	public void offerFile(JingleFile actualFile, String hash) {
+	public void setFileOffer(JingleFile 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.getSize());
 		file.addChild("name").setContent(actualFile.getName());
 	}
+	
+	public Element getFileOffer() {
+		Element description = this.findChild("description", "urn:xmpp:jingle:apps:file-transfer:3");
+		if (description==null) {
+			return null;
+		}
+		Element offer = description.findChild("offer");
+		if (offer==null) {
+			return null;
+		}
+		return offer.findChild("file");
+	}
 
 	public void setCandidates(String transportId, List<Element> canditates) {
 		Element transport = this.findChild("transport", "urn:xmpp:jingle:transports:s5b:1");
@@ -56,4 +68,12 @@ public class Content extends Element {
 			return usedCandidate.getAttribute("cid");
 		}
 	}
+
+	public void addCandidate(Element candidate) {
+		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.addChild(candidate);
+	}
 }