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.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 = -1;
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 cancel();
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 }
99 Log.d(Config.LOGTAG,
100 "sucessfully transmitted file:" + file.getAbsolutePath());
101 if (message.getEncryption() != Message.ENCRYPTION_PGP) {
102 Intent intent = new Intent(
103 Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
104 intent.setData(Uri.fromFile(file));
105 mXmppConnectionService.sendBroadcast(intent);
106 }
107 }
108
109 @Override
110 public void onFileTransferAborted() {
111 JingleConnection.this.sendCancel();
112 JingleConnection.this.cancel();
113 }
114 };
115
116 private OnProxyActivated onProxyActivated = new OnProxyActivated() {
117
118 @Override
119 public void success() {
120 if (initiator.equals(account.getJid())) {
121 Log.d(Config.LOGTAG, "we were initiating. sending file");
122 transport.send(file, onFileTransmissionSatusChanged);
123 } else {
124 transport.receive(file, onFileTransmissionSatusChanged);
125 Log.d(Config.LOGTAG, "we were responding. receiving file");
126 }
127 }
128
129 @Override
130 public void failed() {
131 Log.d(Config.LOGTAG, "proxy activation failed");
132 }
133 };
134
135 public JingleConnection(JingleConnectionManager mJingleConnectionManager) {
136 this.mJingleConnectionManager = mJingleConnectionManager;
137 this.mXmppConnectionService = mJingleConnectionManager
138 .getXmppConnectionService();
139 }
140
141 public String getSessionId() {
142 return this.sessionId;
143 }
144
145 public Account getAccount() {
146 return this.account;
147 }
148
149 public Jid getCounterPart() {
150 return this.message.getCounterpart();
151 }
152
153 public void deliverPacket(JinglePacket packet) {
154 boolean returnResult = true;
155 if (packet.isAction("session-terminate")) {
156 Reason reason = packet.getReason();
157 if (reason != null) {
158 if (reason.hasChild("cancel")) {
159 this.cancel();
160 } else if (reason.hasChild("success")) {
161 this.receiveSuccess();
162 } else {
163 this.cancel();
164 }
165 } else {
166 this.cancel();
167 }
168 } else if (packet.isAction("session-accept")) {
169 returnResult = receiveAccept(packet);
170 } else if (packet.isAction("transport-info")) {
171 returnResult = receiveTransportInfo(packet);
172 } else if (packet.isAction("transport-replace")) {
173 if (packet.getJingleContent().hasIbbTransport()) {
174 returnResult = this.receiveFallbackToIbb(packet);
175 } else {
176 returnResult = false;
177 Log.d(Config.LOGTAG, "trying to fallback to something unknown"
178 + packet.toString());
179 }
180 } else if (packet.isAction("transport-accept")) {
181 returnResult = this.receiveTransportAccept(packet);
182 } else {
183 Log.d(Config.LOGTAG, "packet arrived in connection. action was "
184 + packet.getAction());
185 returnResult = false;
186 }
187 IqPacket response;
188 if (returnResult) {
189 response = packet.generateRespone(IqPacket.TYPE_RESULT);
190
191 } else {
192 response = packet.generateRespone(IqPacket.TYPE_ERROR);
193 }
194 account.getXmppConnection().sendIqPacket(response, null);
195 }
196
197 public void init(Message message) {
198 this.contentCreator = "initiator";
199 this.contentName = this.mJingleConnectionManager.nextRandomId();
200 this.message = message;
201 this.account = message.getConversation().getAccount();
202 this.initiator = this.account.getJid();
203 this.responder = this.message.getCounterpart();
204 this.sessionId = this.mJingleConnectionManager.nextRandomId();
205 if (this.candidates.size() > 0) {
206 this.sendInitRequest();
207 } else {
208 this.mJingleConnectionManager.getPrimaryCandidate(account,
209 new OnPrimaryCandidateFound() {
210
211 @Override
212 public void onPrimaryCandidateFound(boolean success,
213 final JingleCandidate candidate) {
214 if (success) {
215 final JingleSocks5Transport socksConnection = new JingleSocks5Transport(
216 JingleConnection.this, candidate);
217 connections.put(candidate.getCid(),
218 socksConnection);
219 socksConnection
220 .connect(new OnTransportConnected() {
221
222 @Override
223 public void failed() {
224 Log.d(Config.LOGTAG,
225 "connection to our own primary candidete failed");
226 sendInitRequest();
227 }
228
229 @Override
230 public void established() {
231 Log.d(Config.LOGTAG,
232 "succesfully connected to our own primary candidate");
233 mergeCandidate(candidate);
234 sendInitRequest();
235 }
236 });
237 mergeCandidate(candidate);
238 } else {
239 Log.d(Config.LOGTAG,
240 "no primary candidate of our own was found");
241 sendInitRequest();
242 }
243 }
244 });
245 }
246
247 }
248
249 public void init(Account account, JinglePacket packet) {
250 this.mJingleStatus = JINGLE_STATUS_INITIATED;
251 Conversation conversation = this.mXmppConnectionService
252 .findOrCreateConversation(account,
253 packet.getFrom().toBareJid(), false);
254 this.message = new Message(conversation, "", Message.ENCRYPTION_NONE);
255 this.message.setStatus(Message.STATUS_RECEIVED);
256 this.mStatus = Downloadable.STATUS_OFFER;
257 this.message.setDownloadable(this);
258 final Jid from = packet.getFrom();
259 this.message.setCounterpart(from);
260 this.account = account;
261 this.initiator = packet.getFrom();
262 this.responder = this.account.getJid();
263 this.sessionId = packet.getSessionId();
264 Content content = packet.getJingleContent();
265 this.contentCreator = content.getAttribute("creator");
266 this.contentName = content.getAttribute("name");
267 this.transportId = content.getTransportId();
268 this.mergeCandidates(JingleCandidate.parse(content.socks5transport()
269 .getChildren()));
270 this.fileOffer = packet.getJingleContent().getFileOffer();
271 if (fileOffer != null) {
272 Element fileSize = fileOffer.findChild("size");
273 Element fileNameElement = fileOffer.findChild("name");
274 if (fileNameElement != null) {
275 String[] filename = fileNameElement.getContent()
276 .toLowerCase(Locale.US).split("\\.");
277 if (Arrays.asList(VALID_IMAGE_EXTENSIONS).contains(
278 filename[filename.length - 1])) {
279 message.setType(Message.TYPE_IMAGE);
280 } else if (Arrays.asList(VALID_CRYPTO_EXTENSIONS).contains(
281 filename[filename.length - 1])) {
282 if (filename.length == 3) {
283 if (Arrays.asList(VALID_IMAGE_EXTENSIONS).contains(
284 filename[filename.length - 2])) {
285 message.setType(Message.TYPE_IMAGE);
286 } else {
287 message.setType(Message.TYPE_FILE);
288 }
289 if (filename[filename.length - 1].equals("otr")) {
290 message.setEncryption(Message.ENCRYPTION_OTR);
291 } else {
292 message.setEncryption(Message.ENCRYPTION_PGP);
293 }
294 }
295 } else {
296 message.setType(Message.TYPE_FILE);
297 }
298 if (message.getType() == Message.TYPE_FILE) {
299 String suffix = "";
300 if (!fileNameElement.getContent().isEmpty()) {
301 String parts[] = fileNameElement.getContent().split("/");
302 suffix = parts[parts.length - 1];
303 if (message.getEncryption() == Message.ENCRYPTION_OTR && suffix.endsWith(".otr")) {
304 suffix = suffix.substring(0,suffix.length() - 4);
305 }
306 }
307 message.setRelativeFilePath(message.getUuid()+"_"+suffix);
308 }
309 long size = Long.parseLong(fileSize.getContent());
310 message.setBody(Long.toString(size));
311 conversation.add(message);
312 mXmppConnectionService.updateConversationUi();
313 if (size <= this.mJingleConnectionManager
314 .getAutoAcceptFileSize()) {
315 Log.d(Config.LOGTAG, "auto accepting file from "
316 + packet.getFrom());
317 this.acceptedAutomatically = true;
318 this.sendAccept();
319 } else {
320 message.markUnread();
321 Log.d(Config.LOGTAG,
322 "not auto accepting new file offer with size: "
323 + size
324 + " allowed size:"
325 + this.mJingleConnectionManager
326 .getAutoAcceptFileSize());
327 this.mXmppConnectionService.getNotificationService()
328 .push(message);
329 }
330 this.file = this.mXmppConnectionService.getFileBackend()
331 .getFile(message, false);
332 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
333 byte[] key = conversation.getSymmetricKey();
334 if (key == null) {
335 this.sendCancel();
336 this.cancel();
337 return;
338 } else {
339 this.file.setKey(key);
340 }
341 }
342 this.file.setExpectedSize(size);
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 || message.getType() == Message.TYPE_FILE) {
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.getJid());
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.getJid())) {
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.getJid())) {
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.getJid())) {
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.getJid())) {
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 public void updateProgress(int i) {
860 this.mProgress = i;
861 if (SystemClock.elapsedRealtime() - this.mLastGuiRefresh > 1000) {
862 this.mLastGuiRefresh = SystemClock.elapsedRealtime();
863 mXmppConnectionService.updateConversationUi();
864 }
865 }
866
867 interface OnProxyActivated {
868 public void success();
869
870 public void failed();
871 }
872
873 public boolean hasTransportId(String sid) {
874 return sid.equals(this.transportId);
875 }
876
877 public JingleTransport getTransport() {
878 return this.transport;
879 }
880
881 public boolean start() {
882 if (account.getStatus() == Account.STATUS_ONLINE) {
883 if (mJingleStatus == JINGLE_STATUS_INITIATED) {
884 new Thread(new Runnable() {
885
886 @Override
887 public void run() {
888 sendAccept();
889 }
890 }).start();
891 }
892 return true;
893 } else {
894 return false;
895 }
896 }
897
898 @Override
899 public int getStatus() {
900 return this.mStatus;
901 }
902
903 @Override
904 public long getFileSize() {
905 if (this.file != null) {
906 return this.file.getExpectedSize();
907 } else {
908 return 0;
909 }
910 }
911
912 @Override
913 public int getProgress() {
914 return this.mProgress;
915 }
916
917 @Override
918 public String getMimeType() {
919 return this.file.getMimeType();
920 }
921}