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