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