1package eu.siacs.conversations.xmpp.jingle;
2
3import java.net.URLConnection;
4import java.util.ArrayList;
5import java.util.Arrays;
6import java.util.Iterator;
7import java.util.List;
8import java.util.Locale;
9import java.util.Map.Entry;
10import java.util.concurrent.ConcurrentHashMap;
11
12import android.content.Intent;
13import android.net.Uri;
14import android.os.SystemClock;
15import android.util.Log;
16import eu.siacs.conversations.Config;
17import eu.siacs.conversations.entities.Account;
18import eu.siacs.conversations.entities.Conversation;
19import eu.siacs.conversations.entities.Downloadable;
20import eu.siacs.conversations.entities.DownloadableFile;
21import eu.siacs.conversations.entities.Message;
22import eu.siacs.conversations.services.XmppConnectionService;
23import eu.siacs.conversations.xml.Element;
24import eu.siacs.conversations.xmpp.OnIqPacketReceived;
25import eu.siacs.conversations.xmpp.jid.Jid;
26import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
27import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
28import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
29import eu.siacs.conversations.xmpp.stanzas.IqPacket;
30
31public class JingleConnection implements Downloadable {
32
33 private JingleConnectionManager mJingleConnectionManager;
34 private XmppConnectionService mXmppConnectionService;
35
36 protected static final int JINGLE_STATUS_INITIATED = 0;
37 protected static final int JINGLE_STATUS_ACCEPTED = 1;
38 protected static final int JINGLE_STATUS_TERMINATED = 2;
39 protected static final int JINGLE_STATUS_CANCELED = 3;
40 protected static final int JINGLE_STATUS_FINISHED = 4;
41 protected static final int JINGLE_STATUS_TRANSMITTING = 5;
42 protected static final int JINGLE_STATUS_FAILED = 99;
43
44 private int ibbBlockSize = 4096;
45
46 private int mJingleStatus = -1;
47 private int mStatus = Downloadable.STATUS_UNKNOWN;
48 private Message message;
49 private String sessionId;
50 private Account account;
51 private Jid initiator;
52 private Jid responder;
53 private List<JingleCandidate> candidates = new ArrayList<>();
54 private ConcurrentHashMap<String, JingleSocks5Transport> connections = new ConcurrentHashMap<>();
55
56 private String transportId;
57 private Element fileOffer;
58 private DownloadableFile file = null;
59
60 private String contentName;
61 private String contentCreator;
62
63 private int mProgress = 0;
64 private long mLastGuiRefresh = 0;
65
66 private boolean receivedCandidate = false;
67 private boolean sentCandidate = false;
68
69 private boolean acceptedAutomatically = false;
70
71 private JingleTransport transport = null;
72
73 private OnIqPacketReceived responseListener = new OnIqPacketReceived() {
74
75 @Override
76 public void onIqPacketReceived(Account account, IqPacket packet) {
77 if (packet.getType() == IqPacket.TYPE_ERROR) {
78 fail();
79 }
80 }
81 };
82
83 final OnFileTransmissionStatusChanged onFileTransmissionSatusChanged = new OnFileTransmissionStatusChanged() {
84
85 @Override
86 public void onFileTransmitted(DownloadableFile file) {
87 if (responder.equals(account.getJid())) {
88 sendSuccess();
89 if (acceptedAutomatically) {
90 message.markUnread();
91 JingleConnection.this.mXmppConnectionService
92 .getNotificationService().push(message);
93 }
94 mXmppConnectionService.getFileBackend().updateFileParams(message);
95 mXmppConnectionService.databaseBackend.createMessage(message);
96 mXmppConnectionService.markMessage(message,
97 Message.STATUS_RECEIVED);
98 } else {
99 message.setDownloadable(null);
100 if (message.getEncryption() == Message.ENCRYPTION_PGP || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
101 file.delete();
102 }
103 }
104 Log.d(Config.LOGTAG,
105 "sucessfully transmitted file:" + file.getAbsolutePath());
106 if (message.getEncryption() != Message.ENCRYPTION_PGP) {
107 Intent intent = new Intent(
108 Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
109 intent.setData(Uri.fromFile(file));
110 mXmppConnectionService.sendBroadcast(intent);
111 }
112 }
113
114 @Override
115 public void onFileTransferAborted() {
116 JingleConnection.this.sendCancel();
117 JingleConnection.this.fail();
118 }
119 };
120
121 private OnProxyActivated onProxyActivated = new OnProxyActivated() {
122
123 @Override
124 public void success() {
125 if (initiator.equals(account.getJid())) {
126 Log.d(Config.LOGTAG, "we were initiating. sending file");
127 transport.send(file, onFileTransmissionSatusChanged);
128 } else {
129 transport.receive(file, onFileTransmissionSatusChanged);
130 Log.d(Config.LOGTAG, "we were responding. receiving file");
131 }
132 }
133
134 @Override
135 public void failed() {
136 Log.d(Config.LOGTAG, "proxy activation failed");
137 }
138 };
139
140 public JingleConnection(JingleConnectionManager mJingleConnectionManager) {
141 this.mJingleConnectionManager = mJingleConnectionManager;
142 this.mXmppConnectionService = mJingleConnectionManager
143 .getXmppConnectionService();
144 }
145
146 public String getSessionId() {
147 return this.sessionId;
148 }
149
150 public Account getAccount() {
151 return this.account;
152 }
153
154 public Jid getCounterPart() {
155 return this.message.getCounterpart();
156 }
157
158 public void deliverPacket(JinglePacket packet) {
159 boolean returnResult = true;
160 if (packet.isAction("session-terminate")) {
161 Reason reason = packet.getReason();
162 if (reason != null) {
163 if (reason.hasChild("cancel")) {
164 this.fail();
165 } else if (reason.hasChild("success")) {
166 this.receiveSuccess();
167 } else {
168 this.fail();
169 }
170 } else {
171 this.fail();
172 }
173 } else if (packet.isAction("session-accept")) {
174 returnResult = receiveAccept(packet);
175 } else if (packet.isAction("transport-info")) {
176 returnResult = receiveTransportInfo(packet);
177 } else if (packet.isAction("transport-replace")) {
178 if (packet.getJingleContent().hasIbbTransport()) {
179 returnResult = this.receiveFallbackToIbb(packet);
180 } else {
181 returnResult = false;
182 Log.d(Config.LOGTAG, "trying to fallback to something unknown"
183 + packet.toString());
184 }
185 } else if (packet.isAction("transport-accept")) {
186 returnResult = this.receiveTransportAccept(packet);
187 } else {
188 Log.d(Config.LOGTAG, "packet arrived in connection. action was "
189 + packet.getAction());
190 returnResult = false;
191 }
192 IqPacket response;
193 if (returnResult) {
194 response = packet.generateRespone(IqPacket.TYPE_RESULT);
195
196 } else {
197 response = packet.generateRespone(IqPacket.TYPE_ERROR);
198 }
199 account.getXmppConnection().sendIqPacket(response, null);
200 }
201
202 public void init(Message message) {
203 this.contentCreator = "initiator";
204 this.contentName = this.mJingleConnectionManager.nextRandomId();
205 this.message = message;
206 this.message.setDownloadable(this);
207 this.mStatus = Downloadable.STATUS_UPLOADING;
208 this.account = message.getConversation().getAccount();
209 this.initiator = this.account.getJid();
210 this.responder = this.message.getCounterpart();
211 this.sessionId = this.mJingleConnectionManager.nextRandomId();
212 if (this.candidates.size() > 0) {
213 this.sendInitRequest();
214 } else {
215 this.mJingleConnectionManager.getPrimaryCandidate(account,
216 new OnPrimaryCandidateFound() {
217
218 @Override
219 public void onPrimaryCandidateFound(boolean success,
220 final JingleCandidate candidate) {
221 if (success) {
222 final JingleSocks5Transport socksConnection = new JingleSocks5Transport(
223 JingleConnection.this, candidate);
224 connections.put(candidate.getCid(),
225 socksConnection);
226 socksConnection
227 .connect(new OnTransportConnected() {
228
229 @Override
230 public void failed() {
231 Log.d(Config.LOGTAG,
232 "connection to our own primary candidete failed");
233 sendInitRequest();
234 }
235
236 @Override
237 public void established() {
238 Log.d(Config.LOGTAG,
239 "succesfully connected to our own primary candidate");
240 mergeCandidate(candidate);
241 sendInitRequest();
242 }
243 });
244 mergeCandidate(candidate);
245 } else {
246 Log.d(Config.LOGTAG,
247 "no primary candidate of our own was found");
248 sendInitRequest();
249 }
250 }
251 });
252 }
253
254 }
255
256 public void init(Account account, JinglePacket packet) {
257 this.mJingleStatus = JINGLE_STATUS_INITIATED;
258 Conversation conversation = this.mXmppConnectionService
259 .findOrCreateConversation(account,
260 packet.getFrom().toBareJid(), false);
261 this.message = new Message(conversation, "", Message.ENCRYPTION_NONE);
262 this.message.setStatus(Message.STATUS_RECEIVED);
263 this.mStatus = Downloadable.STATUS_OFFER;
264 this.message.setDownloadable(this);
265 final Jid from = packet.getFrom();
266 this.message.setCounterpart(from);
267 this.account = account;
268 this.initiator = packet.getFrom();
269 this.responder = this.account.getJid();
270 this.sessionId = packet.getSessionId();
271 Content content = packet.getJingleContent();
272 this.contentCreator = content.getAttribute("creator");
273 this.contentName = content.getAttribute("name");
274 this.transportId = content.getTransportId();
275 this.mergeCandidates(JingleCandidate.parse(content.socks5transport()
276 .getChildren()));
277 this.fileOffer = packet.getJingleContent().getFileOffer();
278 if (fileOffer != null) {
279 Element fileSize = fileOffer.findChild("size");
280 Element fileNameElement = fileOffer.findChild("name");
281 if (fileNameElement != null) {
282 String[] filename = fileNameElement.getContent()
283 .toLowerCase(Locale.US).split("\\.");
284 if (Arrays.asList(VALID_IMAGE_EXTENSIONS).contains(
285 filename[filename.length - 1])) {
286 message.setType(Message.TYPE_IMAGE);
287 } else if (Arrays.asList(VALID_CRYPTO_EXTENSIONS).contains(
288 filename[filename.length - 1])) {
289 if (filename.length == 3) {
290 if (Arrays.asList(VALID_IMAGE_EXTENSIONS).contains(
291 filename[filename.length - 2])) {
292 message.setType(Message.TYPE_IMAGE);
293 } else {
294 message.setType(Message.TYPE_FILE);
295 }
296 if (filename[filename.length - 1].equals("otr")) {
297 message.setEncryption(Message.ENCRYPTION_OTR);
298 } else {
299 message.setEncryption(Message.ENCRYPTION_PGP);
300 }
301 }
302 } else {
303 message.setType(Message.TYPE_FILE);
304 }
305 if (message.getType() == Message.TYPE_FILE) {
306 String suffix = "";
307 if (!fileNameElement.getContent().isEmpty()) {
308 String parts[] = fileNameElement.getContent().split("/");
309 suffix = parts[parts.length - 1];
310 if (message.getEncryption() == Message.ENCRYPTION_OTR && suffix.endsWith(".otr")) {
311 suffix = suffix.substring(0,suffix.length() - 4);
312 } else if (message.getEncryption() == Message.ENCRYPTION_PGP && (suffix.endsWith(".pgp") || suffix.endsWith(".gpg"))) {
313 suffix = suffix.substring(0,suffix.length() - 4);
314 }
315 }
316 message.setRelativeFilePath(message.getUuid()+"_"+suffix);
317 }
318 long size = Long.parseLong(fileSize.getContent());
319 message.setBody(Long.toString(size));
320 conversation.add(message);
321 mXmppConnectionService.updateConversationUi();
322 if (size <= this.mJingleConnectionManager
323 .getAutoAcceptFileSize()) {
324 Log.d(Config.LOGTAG, "auto accepting file from "
325 + packet.getFrom());
326 this.acceptedAutomatically = true;
327 this.sendAccept();
328 } else {
329 message.markUnread();
330 Log.d(Config.LOGTAG,
331 "not auto accepting new file offer with size: "
332 + size
333 + " allowed size:"
334 + this.mJingleConnectionManager
335 .getAutoAcceptFileSize());
336 this.mXmppConnectionService.getNotificationService()
337 .push(message);
338 }
339 this.file = this.mXmppConnectionService.getFileBackend()
340 .getFile(message, false);
341 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
342 byte[] key = conversation.getSymmetricKey();
343 if (key == null) {
344 this.sendCancel();
345 this.fail();
346 return;
347 } else {
348 this.file.setKey(key);
349 }
350 }
351 this.file.setExpectedSize(size);
352 } else {
353 this.sendCancel();
354 this.fail();
355 }
356 } else {
357 this.sendCancel();
358 this.fail();
359 }
360 }
361
362 private void sendInitRequest() {
363 this.mXmppConnectionService.markMessage(this.message, Message.STATUS_OFFERED);
364 JinglePacket packet = this.bootstrapPacket("session-initiate");
365 Content content = new Content(this.contentCreator, this.contentName);
366 if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
367 content.setTransportId(this.transportId);
368 this.file = this.mXmppConnectionService.getFileBackend().getFile(
369 message, false);
370 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
371 Conversation conversation = this.message.getConversation();
372 this.mXmppConnectionService.renewSymmetricKey(conversation);
373 content.setFileOffer(this.file, true);
374 this.file.setKey(conversation.getSymmetricKey());
375 } else {
376 content.setFileOffer(this.file, false);
377 }
378 this.transportId = this.mJingleConnectionManager.nextRandomId();
379 content.setTransportId(this.transportId);
380 content.socks5transport().setChildren(getCandidatesAsElements());
381 packet.setContent(content);
382 this.sendJinglePacket(packet);
383 this.mJingleStatus = JINGLE_STATUS_INITIATED;
384 }
385 }
386
387 private List<Element> getCandidatesAsElements() {
388 List<Element> elements = new ArrayList<>();
389 for (JingleCandidate c : this.candidates) {
390 elements.add(c.toElement());
391 }
392 return elements;
393 }
394
395 private void sendAccept() {
396 mJingleStatus = JINGLE_STATUS_ACCEPTED;
397 this.mStatus = Downloadable.STATUS_DOWNLOADING;
398 mXmppConnectionService.updateConversationUi();
399 this.mJingleConnectionManager.getPrimaryCandidate(this.account,
400 new OnPrimaryCandidateFound() {
401
402 @Override
403 public void onPrimaryCandidateFound(boolean success,
404 final JingleCandidate candidate) {
405 final JinglePacket packet = bootstrapPacket("session-accept");
406 final Content content = new Content(contentCreator,
407 contentName);
408 content.setFileOffer(fileOffer);
409 content.setTransportId(transportId);
410 if ((success) && (!equalCandidateExists(candidate))) {
411 final JingleSocks5Transport socksConnection = new JingleSocks5Transport(
412 JingleConnection.this, candidate);
413 connections.put(candidate.getCid(), socksConnection);
414 socksConnection.connect(new OnTransportConnected() {
415
416 @Override
417 public void failed() {
418 Log.d(Config.LOGTAG,
419 "connection to our own primary candidate failed");
420 content.socks5transport().setChildren(
421 getCandidatesAsElements());
422 packet.setContent(content);
423 sendJinglePacket(packet);
424 connectNextCandidate();
425 }
426
427 @Override
428 public void established() {
429 Log.d(Config.LOGTAG,
430 "connected to primary candidate");
431 mergeCandidate(candidate);
432 content.socks5transport().setChildren(
433 getCandidatesAsElements());
434 packet.setContent(content);
435 sendJinglePacket(packet);
436 connectNextCandidate();
437 }
438 });
439 } else {
440 Log.d(Config.LOGTAG,
441 "did not find a primary candidate for ourself");
442 content.socks5transport().setChildren(
443 getCandidatesAsElements());
444 packet.setContent(content);
445 sendJinglePacket(packet);
446 connectNextCandidate();
447 }
448 }
449 });
450
451 }
452
453 private JinglePacket bootstrapPacket(String action) {
454 JinglePacket packet = new JinglePacket();
455 packet.setAction(action);
456 packet.setFrom(account.getJid());
457 packet.setTo(this.message.getCounterpart());
458 packet.setSessionId(this.sessionId);
459 packet.setInitiator(this.initiator);
460 return packet;
461 }
462
463 private void sendJinglePacket(JinglePacket packet) {
464 // Log.d(Config.LOGTAG,packet.toString());
465 account.getXmppConnection().sendIqPacket(packet, responseListener);
466 }
467
468 private boolean receiveAccept(JinglePacket packet) {
469 Content content = packet.getJingleContent();
470 mergeCandidates(JingleCandidate.parse(content.socks5transport()
471 .getChildren()));
472 this.mJingleStatus = JINGLE_STATUS_ACCEPTED;
473 mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND);
474 this.connectNextCandidate();
475 return true;
476 }
477
478 private boolean receiveTransportInfo(JinglePacket packet) {
479 Content content = packet.getJingleContent();
480 if (content.hasSocks5Transport()) {
481 if (content.socks5transport().hasChild("activated")) {
482 if ((this.transport != null)
483 && (this.transport instanceof JingleSocks5Transport)) {
484 onProxyActivated.success();
485 } else {
486 String cid = content.socks5transport()
487 .findChild("activated").getAttribute("cid");
488 Log.d(Config.LOGTAG, "received proxy activated (" + cid
489 + ")prior to choosing our own transport");
490 JingleSocks5Transport connection = this.connections
491 .get(cid);
492 if (connection != null) {
493 connection.setActivated(true);
494 } else {
495 Log.d(Config.LOGTAG, "activated connection not found");
496 this.sendCancel();
497 this.fail();
498 }
499 }
500 return true;
501 } else if (content.socks5transport().hasChild("proxy-error")) {
502 onProxyActivated.failed();
503 return true;
504 } else if (content.socks5transport().hasChild("candidate-error")) {
505 Log.d(Config.LOGTAG, "received candidate error");
506 this.receivedCandidate = true;
507 if ((mJingleStatus == JINGLE_STATUS_ACCEPTED)
508 && (this.sentCandidate)) {
509 this.connect();
510 }
511 return true;
512 } else if (content.socks5transport().hasChild("candidate-used")) {
513 String cid = content.socks5transport()
514 .findChild("candidate-used").getAttribute("cid");
515 if (cid != null) {
516 Log.d(Config.LOGTAG, "candidate used by counterpart:" + cid);
517 JingleCandidate candidate = getCandidate(cid);
518 candidate.flagAsUsedByCounterpart();
519 this.receivedCandidate = true;
520 if ((mJingleStatus == JINGLE_STATUS_ACCEPTED)
521 && (this.sentCandidate)) {
522 this.connect();
523 } else {
524 Log.d(Config.LOGTAG,
525 "ignoring because file is already in transmission or we havent sent our candidate yet");
526 }
527 return true;
528 } else {
529 return false;
530 }
531 } else {
532 return false;
533 }
534 } else {
535 return true;
536 }
537 }
538
539 private void connect() {
540 final JingleSocks5Transport connection = chooseConnection();
541 this.transport = connection;
542 if (connection == null) {
543 Log.d(Config.LOGTAG, "could not find suitable candidate");
544 this.disconnectSocks5Connections();
545 if (this.initiator.equals(account.getJid())) {
546 this.sendFallbackToIbb();
547 }
548 } else {
549 this.mJingleStatus = JINGLE_STATUS_TRANSMITTING;
550 if (connection.needsActivation()) {
551 if (connection.getCandidate().isOurs()) {
552 Log.d(Config.LOGTAG, "candidate "
553 + connection.getCandidate().getCid()
554 + " was our proxy. going to activate");
555 IqPacket activation = new IqPacket(IqPacket.TYPE_SET);
556 activation.setTo(connection.getCandidate().getJid());
557 activation.query("http://jabber.org/protocol/bytestreams")
558 .setAttribute("sid", this.getSessionId());
559 activation.query().addChild("activate")
560 .setContent(this.getCounterPart().toString());
561 this.account.getXmppConnection().sendIqPacket(activation,
562 new OnIqPacketReceived() {
563
564 @Override
565 public void onIqPacketReceived(Account account,
566 IqPacket packet) {
567 if (packet.getType() == IqPacket.TYPE_ERROR) {
568 onProxyActivated.failed();
569 } else {
570 onProxyActivated.success();
571 sendProxyActivated(connection
572 .getCandidate().getCid());
573 }
574 }
575 });
576 } else {
577 Log.d(Config.LOGTAG,
578 "candidate "
579 + connection.getCandidate().getCid()
580 + " was a proxy. waiting for other party to activate");
581 }
582 } else {
583 if (initiator.equals(account.getJid())) {
584 Log.d(Config.LOGTAG, "we were initiating. sending file");
585 connection.send(file, onFileTransmissionSatusChanged);
586 } else {
587 Log.d(Config.LOGTAG, "we were responding. receiving file");
588 connection.receive(file, onFileTransmissionSatusChanged);
589 }
590 }
591 }
592 }
593
594 private JingleSocks5Transport chooseConnection() {
595 JingleSocks5Transport connection = null;
596 for (Entry<String, JingleSocks5Transport> cursor : connections
597 .entrySet()) {
598 JingleSocks5Transport currentConnection = cursor.getValue();
599 // Log.d(Config.LOGTAG,"comparing candidate: "+currentConnection.getCandidate().toString());
600 if (currentConnection.isEstablished()
601 && (currentConnection.getCandidate().isUsedByCounterpart() || (!currentConnection
602 .getCandidate().isOurs()))) {
603 // Log.d(Config.LOGTAG,"is usable");
604 if (connection == null) {
605 connection = currentConnection;
606 } else {
607 if (connection.getCandidate().getPriority() < currentConnection
608 .getCandidate().getPriority()) {
609 connection = currentConnection;
610 } else if (connection.getCandidate().getPriority() == currentConnection
611 .getCandidate().getPriority()) {
612 // Log.d(Config.LOGTAG,"found two candidates with same priority");
613 if (initiator.equals(account.getJid())) {
614 if (currentConnection.getCandidate().isOurs()) {
615 connection = currentConnection;
616 }
617 } else {
618 if (!currentConnection.getCandidate().isOurs()) {
619 connection = currentConnection;
620 }
621 }
622 }
623 }
624 }
625 }
626 return connection;
627 }
628
629 private void sendSuccess() {
630 JinglePacket packet = bootstrapPacket("session-terminate");
631 Reason reason = new Reason();
632 reason.addChild("success");
633 packet.setReason(reason);
634 this.sendJinglePacket(packet);
635 this.disconnectSocks5Connections();
636 this.mJingleStatus = JINGLE_STATUS_FINISHED;
637 this.message.setStatus(Message.STATUS_RECEIVED);
638 this.message.setDownloadable(null);
639 this.mXmppConnectionService.updateMessage(message);
640 this.mJingleConnectionManager.finishConnection(this);
641 }
642
643 private void sendFallbackToIbb() {
644 Log.d(Config.LOGTAG, "sending fallback to ibb");
645 JinglePacket packet = this.bootstrapPacket("transport-replace");
646 Content content = new Content(this.contentCreator, this.contentName);
647 this.transportId = this.mJingleConnectionManager.nextRandomId();
648 content.setTransportId(this.transportId);
649 content.ibbTransport().setAttribute("block-size",
650 Integer.toString(this.ibbBlockSize));
651 packet.setContent(content);
652 this.sendJinglePacket(packet);
653 }
654
655 private boolean receiveFallbackToIbb(JinglePacket packet) {
656 Log.d(Config.LOGTAG, "receiving fallack to ibb");
657 String receivedBlockSize = packet.getJingleContent().ibbTransport()
658 .getAttribute("block-size");
659 if (receivedBlockSize != null) {
660 int bs = Integer.parseInt(receivedBlockSize);
661 if (bs > this.ibbBlockSize) {
662 this.ibbBlockSize = bs;
663 }
664 }
665 this.transportId = packet.getJingleContent().getTransportId();
666 this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize);
667 this.transport.receive(file, onFileTransmissionSatusChanged);
668 JinglePacket answer = bootstrapPacket("transport-accept");
669 Content content = new Content("initiator", "a-file-offer");
670 content.setTransportId(this.transportId);
671 content.ibbTransport().setAttribute("block-size",
672 Integer.toString(this.ibbBlockSize));
673 answer.setContent(content);
674 this.sendJinglePacket(answer);
675 return true;
676 }
677
678 private boolean receiveTransportAccept(JinglePacket packet) {
679 if (packet.getJingleContent().hasIbbTransport()) {
680 String receivedBlockSize = packet.getJingleContent().ibbTransport()
681 .getAttribute("block-size");
682 if (receivedBlockSize != null) {
683 int bs = Integer.parseInt(receivedBlockSize);
684 if (bs > this.ibbBlockSize) {
685 this.ibbBlockSize = bs;
686 }
687 }
688 this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize);
689 this.transport.connect(new OnTransportConnected() {
690
691 @Override
692 public void failed() {
693 Log.d(Config.LOGTAG, "ibb open failed");
694 }
695
696 @Override
697 public void established() {
698 JingleConnection.this.transport.send(file,
699 onFileTransmissionSatusChanged);
700 }
701 });
702 return true;
703 } else {
704 return false;
705 }
706 }
707
708 private void receiveSuccess() {
709 this.mJingleStatus = JINGLE_STATUS_FINISHED;
710 this.mXmppConnectionService.markMessage(this.message,
711 Message.STATUS_SEND);
712 this.disconnectSocks5Connections();
713 this.mJingleConnectionManager.finishConnection(this);
714 }
715
716 public void cancel() {
717 this.disconnectSocks5Connections();
718 if (this.transport != null && this.transport instanceof JingleInbandTransport) {
719 this.transport.disconnect();
720 }
721 this.sendCancel();
722 this.mJingleConnectionManager.finishConnection(this);
723 if (this.responder.equals(account.getJid())) {
724 this.mStatus = Downloadable.STATUS_FAILED;
725 this.mXmppConnectionService.updateConversationUi();
726 } else {
727 this.mXmppConnectionService.markMessage(this.message,
728 Message.STATUS_SEND_FAILED);
729 this.message.setDownloadable(null);
730 }
731 }
732
733 private void fail() {
734 this.mJingleStatus = JINGLE_STATUS_FAILED;
735 this.disconnectSocks5Connections();
736 if (this.message != null) {
737 if (this.responder.equals(account.getJid())) {
738 this.mStatus = Downloadable.STATUS_FAILED;
739 this.mXmppConnectionService.updateConversationUi();
740 } else {
741 this.mXmppConnectionService.markMessage(this.message,
742 Message.STATUS_SEND_FAILED);
743 this.message.setDownloadable(null);
744 }
745 }
746 this.mJingleConnectionManager.finishConnection(this);
747 }
748
749 private void sendCancel() {
750 JinglePacket packet = bootstrapPacket("session-terminate");
751 Reason reason = new Reason();
752 reason.addChild("cancel");
753 packet.setReason(reason);
754 this.sendJinglePacket(packet);
755 }
756
757 private void connectNextCandidate() {
758 for (JingleCandidate candidate : this.candidates) {
759 if ((!connections.containsKey(candidate.getCid()) && (!candidate
760 .isOurs()))) {
761 this.connectWithCandidate(candidate);
762 return;
763 }
764 }
765 this.sendCandidateError();
766 }
767
768 private void connectWithCandidate(final JingleCandidate candidate) {
769 final JingleSocks5Transport socksConnection = new JingleSocks5Transport(
770 this, candidate);
771 connections.put(candidate.getCid(), socksConnection);
772 socksConnection.connect(new OnTransportConnected() {
773
774 @Override
775 public void failed() {
776 Log.d(Config.LOGTAG,
777 "connection failed with " + candidate.getHost() + ":"
778 + candidate.getPort());
779 connectNextCandidate();
780 }
781
782 @Override
783 public void established() {
784 Log.d(Config.LOGTAG,
785 "established connection with " + candidate.getHost()
786 + ":" + candidate.getPort());
787 sendCandidateUsed(candidate.getCid());
788 }
789 });
790 }
791
792 private void disconnectSocks5Connections() {
793 Iterator<Entry<String, JingleSocks5Transport>> it = this.connections
794 .entrySet().iterator();
795 while (it.hasNext()) {
796 Entry<String, JingleSocks5Transport> pairs = it.next();
797 pairs.getValue().disconnect();
798 it.remove();
799 }
800 }
801
802 private void sendProxyActivated(String cid) {
803 JinglePacket packet = bootstrapPacket("transport-info");
804 Content content = new Content(this.contentCreator, this.contentName);
805 content.setTransportId(this.transportId);
806 content.socks5transport().addChild("activated")
807 .setAttribute("cid", cid);
808 packet.setContent(content);
809 this.sendJinglePacket(packet);
810 }
811
812 private void sendCandidateUsed(final String cid) {
813 JinglePacket packet = bootstrapPacket("transport-info");
814 Content content = new Content(this.contentCreator, this.contentName);
815 content.setTransportId(this.transportId);
816 content.socks5transport().addChild("candidate-used")
817 .setAttribute("cid", cid);
818 packet.setContent(content);
819 this.sentCandidate = true;
820 if ((receivedCandidate) && (mJingleStatus == JINGLE_STATUS_ACCEPTED)) {
821 connect();
822 }
823 this.sendJinglePacket(packet);
824 }
825
826 private void sendCandidateError() {
827 JinglePacket packet = bootstrapPacket("transport-info");
828 Content content = new Content(this.contentCreator, this.contentName);
829 content.setTransportId(this.transportId);
830 content.socks5transport().addChild("candidate-error");
831 packet.setContent(content);
832 this.sentCandidate = true;
833 if ((receivedCandidate) && (mJingleStatus == JINGLE_STATUS_ACCEPTED)) {
834 connect();
835 }
836 this.sendJinglePacket(packet);
837 }
838
839 public Jid getInitiator() {
840 return this.initiator;
841 }
842
843 public Jid getResponder() {
844 return this.responder;
845 }
846
847 public int getJingleStatus() {
848 return this.mJingleStatus;
849 }
850
851 private boolean equalCandidateExists(JingleCandidate candidate) {
852 for (JingleCandidate c : this.candidates) {
853 if (c.equalValues(candidate)) {
854 return true;
855 }
856 }
857 return false;
858 }
859
860 private void mergeCandidate(JingleCandidate candidate) {
861 for (JingleCandidate c : this.candidates) {
862 if (c.equals(candidate)) {
863 return;
864 }
865 }
866 this.candidates.add(candidate);
867 }
868
869 private void mergeCandidates(List<JingleCandidate> candidates) {
870 for (JingleCandidate c : candidates) {
871 mergeCandidate(c);
872 }
873 }
874
875 private JingleCandidate getCandidate(String cid) {
876 for (JingleCandidate c : this.candidates) {
877 if (c.getCid().equals(cid)) {
878 return c;
879 }
880 }
881 return null;
882 }
883
884 public void updateProgress(int i) {
885 this.mProgress = i;
886 if (SystemClock.elapsedRealtime() - this.mLastGuiRefresh > Config.PROGRESS_UI_UPDATE_INTERVAL) {
887 this.mLastGuiRefresh = SystemClock.elapsedRealtime();
888 mXmppConnectionService.updateConversationUi();
889 }
890 }
891
892 interface OnProxyActivated {
893 public void success();
894
895 public void failed();
896 }
897
898 public boolean hasTransportId(String sid) {
899 return sid.equals(this.transportId);
900 }
901
902 public JingleTransport getTransport() {
903 return this.transport;
904 }
905
906 public boolean start() {
907 if (account.getStatus() == Account.STATUS_ONLINE) {
908 if (mJingleStatus == JINGLE_STATUS_INITIATED) {
909 new Thread(new Runnable() {
910
911 @Override
912 public void run() {
913 sendAccept();
914 }
915 }).start();
916 }
917 return true;
918 } else {
919 return false;
920 }
921 }
922
923 @Override
924 public int getStatus() {
925 return this.mStatus;
926 }
927
928 @Override
929 public long getFileSize() {
930 if (this.file != null) {
931 return this.file.getExpectedSize();
932 } else {
933 return 0;
934 }
935 }
936
937 @Override
938 public int getProgress() {
939 return this.mProgress;
940 }
941
942 @Override
943 public String getMimeType() {
944 if (this.message.getType() == Message.TYPE_FILE) {
945 String mime = null;
946 String path = this.message.getRelativeFilePath();
947 if (path != null && !this.message.getRelativeFilePath().isEmpty()) {
948 mime = URLConnection.guessContentTypeFromName(this.message.getRelativeFilePath());
949 if (mime!=null) {
950 return mime;
951 } else {
952 return "";
953 }
954 } else {
955 return "";
956 }
957 } else {
958 return "image/webp";
959 }
960 }
961}