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