diff options
10 files changed, 234 insertions, 78 deletions
diff --git a/res/values/arrays.xml b/res/values/arrays.xml index 2210fbb88..09fd3cc70 100644 --- a/res/values/arrays.xml +++ b/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> diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index 0e746a3e5..4a500d709 100644 --- a/res/xml/preferences.xml +++ b/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"> diff --git a/src/eu/siacs/conversations/persistance/FileBackend.java b/src/eu/siacs/conversations/persistance/FileBackend.java index 1cf1b26cf..59373e74c 100644 --- a/src/eu/siacs/conversations/persistance/FileBackend.java +++ b/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); diff --git a/src/eu/siacs/conversations/services/XmppConnectionService.java b/src/eu/siacs/conversations/services/XmppConnectionService.java index 986087d25..63ab0897e 100644 --- a/src/eu/siacs/conversations/services/XmppConnectionService.java +++ b/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()); + } } diff --git a/src/eu/siacs/conversations/utils/UIHelper.java b/src/eu/siacs/conversations/utils/UIHelper.java index fbac8c7fc..312f42b35 100644 --- a/src/eu/siacs/conversations/utils/UIHelper.java +++ b/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) { diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java b/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java index c5b7b60c5..a97c3ac3f 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java +++ b/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); + } + } } diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index 45323e44c..7df27d4d3 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -122,4 +122,8 @@ public class JingleConnectionManager { public String nextRandomId() { return new BigInteger(50, random).toString(32); } + + public long getAutoAcceptFileSize() { + return this.xmppConnectionService.getPreferences().getLong("auto_accept_file_size", 0); + } } diff --git a/src/eu/siacs/conversations/xmpp/jingle/OnSocksConnection.java b/src/eu/siacs/conversations/xmpp/jingle/OnSocksConnection.java new file mode 100644 index 000000000..88771997c --- /dev/null +++ b/src/eu/siacs/conversations/xmpp/jingle/OnSocksConnection.java @@ -0,0 +1,6 @@ +package eu.siacs.conversations.xmpp.jingle; + +public interface OnSocksConnection { + public void failed(); + public void established(); +} diff --git a/src/eu/siacs/conversations/xmpp/jingle/SocksConnection.java b/src/eu/siacs/conversations/xmpp/jingle/SocksConnection.java index 6a3a36484..d4cb432a1 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/SocksConnection.java +++ b/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; + } } diff --git a/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java b/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java index cefcde365..c9015d391 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java +++ b/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); + } } |