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