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 } else if (message.getEncryption() == Message.ENCRYPTION_PGP && (suffix.endsWith(".pgp") || suffix.endsWith(".gpg"))) {
310 suffix = suffix.substring(0,suffix.length() - 4);
311 }
312 }
313 message.setRelativeFilePath(message.getUuid()+"_"+suffix);
314 }
315 long size = Long.parseLong(fileSize.getContent());
316 message.setBody(Long.toString(size));
317 conversation.add(message);
318 mXmppConnectionService.updateConversationUi();
319 if (size <= this.mJingleConnectionManager
320 .getAutoAcceptFileSize()) {
321 Log.d(Config.LOGTAG, "auto accepting file from "
322 + packet.getFrom());
323 this.acceptedAutomatically = true;
324 this.sendAccept();
325 } else {
326 message.markUnread();
327 Log.d(Config.LOGTAG,
328 "not auto accepting new file offer with size: "
329 + size
330 + " allowed size:"
331 + this.mJingleConnectionManager
332 .getAutoAcceptFileSize());
333 this.mXmppConnectionService.getNotificationService()
334 .push(message);
335 }
336 this.file = this.mXmppConnectionService.getFileBackend()
337 .getFile(message, false);
338 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
339 byte[] key = conversation.getSymmetricKey();
340 if (key == null) {
341 this.sendCancel();
342 this.cancel();
343 return;
344 } else {
345 this.file.setKey(key);
346 }
347 }
348 this.file.setExpectedSize(size);
349 } else {
350 this.sendCancel();
351 this.cancel();
352 }
353 } else {
354 this.sendCancel();
355 this.cancel();
356 }
357 }
358
359 private void sendInitRequest() {
360 this.mXmppConnectionService.markMessage(this.message, Message.STATUS_OFFERED);
361 JinglePacket packet = this.bootstrapPacket("session-initiate");
362 Content content = new Content(this.contentCreator, this.contentName);
363 if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
364 content.setTransportId(this.transportId);
365 this.file = this.mXmppConnectionService.getFileBackend().getFile(
366 message, false);
367 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
368 Conversation conversation = this.message.getConversation();
369 this.mXmppConnectionService.renewSymmetricKey(conversation);
370 content.setFileOffer(this.file, true);
371 this.file.setKey(conversation.getSymmetricKey());
372 } else {
373 content.setFileOffer(this.file, false);
374 }
375 this.transportId = this.mJingleConnectionManager.nextRandomId();
376 content.setTransportId(this.transportId);
377 content.socks5transport().setChildren(getCandidatesAsElements());
378 packet.setContent(content);
379 this.sendJinglePacket(packet);
380 this.mJingleStatus = JINGLE_STATUS_INITIATED;
381 }
382 }
383
384 private List<Element> getCandidatesAsElements() {
385 List<Element> elements = new ArrayList<>();
386 for (JingleCandidate c : this.candidates) {
387 elements.add(c.toElement());
388 }
389 return elements;
390 }
391
392 private void sendAccept() {
393 mJingleStatus = JINGLE_STATUS_ACCEPTED;
394 this.mStatus = Downloadable.STATUS_DOWNLOADING;
395 mXmppConnectionService.updateConversationUi();
396 this.mJingleConnectionManager.getPrimaryCandidate(this.account,
397 new OnPrimaryCandidateFound() {
398
399 @Override
400 public void onPrimaryCandidateFound(boolean success,
401 final JingleCandidate candidate) {
402 final JinglePacket packet = bootstrapPacket("session-accept");
403 final Content content = new Content(contentCreator,
404 contentName);
405 content.setFileOffer(fileOffer);
406 content.setTransportId(transportId);
407 if ((success) && (!equalCandidateExists(candidate))) {
408 final JingleSocks5Transport socksConnection = new JingleSocks5Transport(
409 JingleConnection.this, candidate);
410 connections.put(candidate.getCid(), socksConnection);
411 socksConnection.connect(new OnTransportConnected() {
412
413 @Override
414 public void failed() {
415 Log.d(Config.LOGTAG,
416 "connection to our own primary candidate failed");
417 content.socks5transport().setChildren(
418 getCandidatesAsElements());
419 packet.setContent(content);
420 sendJinglePacket(packet);
421 connectNextCandidate();
422 }
423
424 @Override
425 public void established() {
426 Log.d(Config.LOGTAG,
427 "connected to primary candidate");
428 mergeCandidate(candidate);
429 content.socks5transport().setChildren(
430 getCandidatesAsElements());
431 packet.setContent(content);
432 sendJinglePacket(packet);
433 connectNextCandidate();
434 }
435 });
436 } else {
437 Log.d(Config.LOGTAG,
438 "did not find a primary candidate for ourself");
439 content.socks5transport().setChildren(
440 getCandidatesAsElements());
441 packet.setContent(content);
442 sendJinglePacket(packet);
443 connectNextCandidate();
444 }
445 }
446 });
447
448 }
449
450 private JinglePacket bootstrapPacket(String action) {
451 JinglePacket packet = new JinglePacket();
452 packet.setAction(action);
453 packet.setFrom(account.getJid());
454 packet.setTo(this.message.getCounterpart());
455 packet.setSessionId(this.sessionId);
456 packet.setInitiator(this.initiator);
457 return packet;
458 }
459
460 private void sendJinglePacket(JinglePacket packet) {
461 // Log.d(Config.LOGTAG,packet.toString());
462 account.getXmppConnection().sendIqPacket(packet, responseListener);
463 }
464
465 private boolean receiveAccept(JinglePacket packet) {
466 Content content = packet.getJingleContent();
467 mergeCandidates(JingleCandidate.parse(content.socks5transport()
468 .getChildren()));
469 this.mJingleStatus = JINGLE_STATUS_ACCEPTED;
470 mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND);
471 this.connectNextCandidate();
472 return true;
473 }
474
475 private boolean receiveTransportInfo(JinglePacket packet) {
476 Content content = packet.getJingleContent();
477 if (content.hasSocks5Transport()) {
478 if (content.socks5transport().hasChild("activated")) {
479 if ((this.transport != null)
480 && (this.transport instanceof JingleSocks5Transport)) {
481 onProxyActivated.success();
482 } else {
483 String cid = content.socks5transport()
484 .findChild("activated").getAttribute("cid");
485 Log.d(Config.LOGTAG, "received proxy activated (" + cid
486 + ")prior to choosing our own transport");
487 JingleSocks5Transport connection = this.connections
488 .get(cid);
489 if (connection != null) {
490 connection.setActivated(true);
491 } else {
492 Log.d(Config.LOGTAG, "activated connection not found");
493 this.sendCancel();
494 this.cancel();
495 }
496 }
497 return true;
498 } else if (content.socks5transport().hasChild("proxy-error")) {
499 onProxyActivated.failed();
500 return true;
501 } else if (content.socks5transport().hasChild("candidate-error")) {
502 Log.d(Config.LOGTAG, "received candidate error");
503 this.receivedCandidate = true;
504 if ((mJingleStatus == JINGLE_STATUS_ACCEPTED)
505 && (this.sentCandidate)) {
506 this.connect();
507 }
508 return true;
509 } else if (content.socks5transport().hasChild("candidate-used")) {
510 String cid = content.socks5transport()
511 .findChild("candidate-used").getAttribute("cid");
512 if (cid != null) {
513 Log.d(Config.LOGTAG, "candidate used by counterpart:" + cid);
514 JingleCandidate candidate = getCandidate(cid);
515 candidate.flagAsUsedByCounterpart();
516 this.receivedCandidate = true;
517 if ((mJingleStatus == JINGLE_STATUS_ACCEPTED)
518 && (this.sentCandidate)) {
519 this.connect();
520 } else {
521 Log.d(Config.LOGTAG,
522 "ignoring because file is already in transmission or we havent sent our candidate yet");
523 }
524 return true;
525 } else {
526 return false;
527 }
528 } else {
529 return false;
530 }
531 } else {
532 return true;
533 }
534 }
535
536 private void connect() {
537 final JingleSocks5Transport connection = chooseConnection();
538 this.transport = connection;
539 if (connection == null) {
540 Log.d(Config.LOGTAG, "could not find suitable candidate");
541 this.disconnect();
542 if (this.initiator.equals(account.getJid())) {
543 this.sendFallbackToIbb();
544 }
545 } else {
546 this.mJingleStatus = JINGLE_STATUS_TRANSMITTING;
547 if (connection.needsActivation()) {
548 if (connection.getCandidate().isOurs()) {
549 Log.d(Config.LOGTAG, "candidate "
550 + connection.getCandidate().getCid()
551 + " was our proxy. going to activate");
552 IqPacket activation = new IqPacket(IqPacket.TYPE_SET);
553 activation.setTo(connection.getCandidate().getJid());
554 activation.query("http://jabber.org/protocol/bytestreams")
555 .setAttribute("sid", this.getSessionId());
556 activation.query().addChild("activate")
557 .setContent(this.getCounterPart().toString());
558 this.account.getXmppConnection().sendIqPacket(activation,
559 new OnIqPacketReceived() {
560
561 @Override
562 public void onIqPacketReceived(Account account,
563 IqPacket packet) {
564 if (packet.getType() == IqPacket.TYPE_ERROR) {
565 onProxyActivated.failed();
566 } else {
567 onProxyActivated.success();
568 sendProxyActivated(connection
569 .getCandidate().getCid());
570 }
571 }
572 });
573 } else {
574 Log.d(Config.LOGTAG,
575 "candidate "
576 + connection.getCandidate().getCid()
577 + " was a proxy. waiting for other party to activate");
578 }
579 } else {
580 if (initiator.equals(account.getJid())) {
581 Log.d(Config.LOGTAG, "we were initiating. sending file");
582 connection.send(file, onFileTransmissionSatusChanged);
583 } else {
584 Log.d(Config.LOGTAG, "we were responding. receiving file");
585 connection.receive(file, onFileTransmissionSatusChanged);
586 }
587 }
588 }
589 }
590
591 private JingleSocks5Transport chooseConnection() {
592 JingleSocks5Transport connection = null;
593 for (Entry<String, JingleSocks5Transport> cursor : connections
594 .entrySet()) {
595 JingleSocks5Transport currentConnection = cursor.getValue();
596 // Log.d(Config.LOGTAG,"comparing candidate: "+currentConnection.getCandidate().toString());
597 if (currentConnection.isEstablished()
598 && (currentConnection.getCandidate().isUsedByCounterpart() || (!currentConnection
599 .getCandidate().isOurs()))) {
600 // Log.d(Config.LOGTAG,"is usable");
601 if (connection == null) {
602 connection = currentConnection;
603 } else {
604 if (connection.getCandidate().getPriority() < currentConnection
605 .getCandidate().getPriority()) {
606 connection = currentConnection;
607 } else if (connection.getCandidate().getPriority() == currentConnection
608 .getCandidate().getPriority()) {
609 // Log.d(Config.LOGTAG,"found two candidates with same priority");
610 if (initiator.equals(account.getJid())) {
611 if (currentConnection.getCandidate().isOurs()) {
612 connection = currentConnection;
613 }
614 } else {
615 if (!currentConnection.getCandidate().isOurs()) {
616 connection = currentConnection;
617 }
618 }
619 }
620 }
621 }
622 }
623 return connection;
624 }
625
626 private void sendSuccess() {
627 JinglePacket packet = bootstrapPacket("session-terminate");
628 Reason reason = new Reason();
629 reason.addChild("success");
630 packet.setReason(reason);
631 this.sendJinglePacket(packet);
632 this.disconnect();
633 this.mJingleStatus = JINGLE_STATUS_FINISHED;
634 this.message.setStatus(Message.STATUS_RECEIVED);
635 this.message.setDownloadable(null);
636 this.mXmppConnectionService.updateMessage(message);
637 this.mJingleConnectionManager.finishConnection(this);
638 }
639
640 private void sendFallbackToIbb() {
641 Log.d(Config.LOGTAG, "sending fallback to ibb");
642 JinglePacket packet = this.bootstrapPacket("transport-replace");
643 Content content = new Content(this.contentCreator, this.contentName);
644 this.transportId = this.mJingleConnectionManager.nextRandomId();
645 content.setTransportId(this.transportId);
646 content.ibbTransport().setAttribute("block-size",
647 Integer.toString(this.ibbBlockSize));
648 packet.setContent(content);
649 this.sendJinglePacket(packet);
650 }
651
652 private boolean receiveFallbackToIbb(JinglePacket packet) {
653 Log.d(Config.LOGTAG, "receiving fallack to ibb");
654 String receivedBlockSize = packet.getJingleContent().ibbTransport()
655 .getAttribute("block-size");
656 if (receivedBlockSize != null) {
657 int bs = Integer.parseInt(receivedBlockSize);
658 if (bs > this.ibbBlockSize) {
659 this.ibbBlockSize = bs;
660 }
661 }
662 this.transportId = packet.getJingleContent().getTransportId();
663 this.transport = new JingleInbandTransport(this.account,
664 this.responder, this.transportId, this.ibbBlockSize);
665 this.transport.receive(file, onFileTransmissionSatusChanged);
666 JinglePacket answer = bootstrapPacket("transport-accept");
667 Content content = new Content("initiator", "a-file-offer");
668 content.setTransportId(this.transportId);
669 content.ibbTransport().setAttribute("block-size",
670 Integer.toString(this.ibbBlockSize));
671 answer.setContent(content);
672 this.sendJinglePacket(answer);
673 return true;
674 }
675
676 private boolean receiveTransportAccept(JinglePacket packet) {
677 if (packet.getJingleContent().hasIbbTransport()) {
678 String receivedBlockSize = packet.getJingleContent().ibbTransport()
679 .getAttribute("block-size");
680 if (receivedBlockSize != null) {
681 int bs = Integer.parseInt(receivedBlockSize);
682 if (bs > this.ibbBlockSize) {
683 this.ibbBlockSize = bs;
684 }
685 }
686 this.transport = new JingleInbandTransport(this.account,
687 this.responder, this.transportId, this.ibbBlockSize);
688 this.transport.connect(new OnTransportConnected() {
689
690 @Override
691 public void failed() {
692 Log.d(Config.LOGTAG, "ibb open failed");
693 }
694
695 @Override
696 public void established() {
697 JingleConnection.this.transport.send(file,
698 onFileTransmissionSatusChanged);
699 }
700 });
701 return true;
702 } else {
703 return false;
704 }
705 }
706
707 private void receiveSuccess() {
708 this.mJingleStatus = JINGLE_STATUS_FINISHED;
709 this.mXmppConnectionService.markMessage(this.message,
710 Message.STATUS_SEND);
711 this.disconnect();
712 this.mJingleConnectionManager.finishConnection(this);
713 }
714
715 public void cancel() {
716 this.mJingleStatus = JINGLE_STATUS_CANCELED;
717 this.disconnect();
718 if (this.message != null) {
719 if (this.responder.equals(account.getJid())) {
720 this.mStatus = Downloadable.STATUS_FAILED;
721 this.mXmppConnectionService.updateConversationUi();
722 } else {
723 this.mXmppConnectionService.markMessage(this.message,
724 Message.STATUS_SEND_FAILED);
725 }
726 }
727 this.mJingleConnectionManager.finishConnection(this);
728 }
729
730 private void sendCancel() {
731 JinglePacket packet = bootstrapPacket("session-terminate");
732 Reason reason = new Reason();
733 reason.addChild("cancel");
734 packet.setReason(reason);
735 this.sendJinglePacket(packet);
736 }
737
738 private void connectNextCandidate() {
739 for (JingleCandidate candidate : this.candidates) {
740 if ((!connections.containsKey(candidate.getCid()) && (!candidate
741 .isOurs()))) {
742 this.connectWithCandidate(candidate);
743 return;
744 }
745 }
746 this.sendCandidateError();
747 }
748
749 private void connectWithCandidate(final JingleCandidate candidate) {
750 final JingleSocks5Transport socksConnection = new JingleSocks5Transport(
751 this, candidate);
752 connections.put(candidate.getCid(), socksConnection);
753 socksConnection.connect(new OnTransportConnected() {
754
755 @Override
756 public void failed() {
757 Log.d(Config.LOGTAG,
758 "connection failed with " + candidate.getHost() + ":"
759 + candidate.getPort());
760 connectNextCandidate();
761 }
762
763 @Override
764 public void established() {
765 Log.d(Config.LOGTAG,
766 "established connection with " + candidate.getHost()
767 + ":" + candidate.getPort());
768 sendCandidateUsed(candidate.getCid());
769 }
770 });
771 }
772
773 private void disconnect() {
774 Iterator<Entry<String, JingleSocks5Transport>> it = this.connections
775 .entrySet().iterator();
776 while (it.hasNext()) {
777 Entry<String, JingleSocks5Transport> pairs = it.next();
778 pairs.getValue().disconnect();
779 it.remove();
780 }
781 }
782
783 private void sendProxyActivated(String cid) {
784 JinglePacket packet = bootstrapPacket("transport-info");
785 Content content = new Content(this.contentCreator, this.contentName);
786 content.setTransportId(this.transportId);
787 content.socks5transport().addChild("activated")
788 .setAttribute("cid", cid);
789 packet.setContent(content);
790 this.sendJinglePacket(packet);
791 }
792
793 private void sendCandidateUsed(final String cid) {
794 JinglePacket packet = bootstrapPacket("transport-info");
795 Content content = new Content(this.contentCreator, this.contentName);
796 content.setTransportId(this.transportId);
797 content.socks5transport().addChild("candidate-used")
798 .setAttribute("cid", cid);
799 packet.setContent(content);
800 this.sentCandidate = true;
801 if ((receivedCandidate) && (mJingleStatus == JINGLE_STATUS_ACCEPTED)) {
802 connect();
803 }
804 this.sendJinglePacket(packet);
805 }
806
807 private void sendCandidateError() {
808 JinglePacket packet = bootstrapPacket("transport-info");
809 Content content = new Content(this.contentCreator, this.contentName);
810 content.setTransportId(this.transportId);
811 content.socks5transport().addChild("candidate-error");
812 packet.setContent(content);
813 this.sentCandidate = true;
814 if ((receivedCandidate) && (mJingleStatus == JINGLE_STATUS_ACCEPTED)) {
815 connect();
816 }
817 this.sendJinglePacket(packet);
818 }
819
820 public Jid getInitiator() {
821 return this.initiator;
822 }
823
824 public Jid getResponder() {
825 return this.responder;
826 }
827
828 public int getJingleStatus() {
829 return this.mJingleStatus;
830 }
831
832 private boolean equalCandidateExists(JingleCandidate candidate) {
833 for (JingleCandidate c : this.candidates) {
834 if (c.equalValues(candidate)) {
835 return true;
836 }
837 }
838 return false;
839 }
840
841 private void mergeCandidate(JingleCandidate candidate) {
842 for (JingleCandidate c : this.candidates) {
843 if (c.equals(candidate)) {
844 return;
845 }
846 }
847 this.candidates.add(candidate);
848 }
849
850 private void mergeCandidates(List<JingleCandidate> candidates) {
851 for (JingleCandidate c : candidates) {
852 mergeCandidate(c);
853 }
854 }
855
856 private JingleCandidate getCandidate(String cid) {
857 for (JingleCandidate c : this.candidates) {
858 if (c.getCid().equals(cid)) {
859 return c;
860 }
861 }
862 return null;
863 }
864
865 public void updateProgress(int i) {
866 this.mProgress = i;
867 if (SystemClock.elapsedRealtime() - this.mLastGuiRefresh > Config.PROGRESS_UI_UPDATE_INTERVAL) {
868 this.mLastGuiRefresh = SystemClock.elapsedRealtime();
869 mXmppConnectionService.updateConversationUi();
870 }
871 }
872
873 interface OnProxyActivated {
874 public void success();
875
876 public void failed();
877 }
878
879 public boolean hasTransportId(String sid) {
880 return sid.equals(this.transportId);
881 }
882
883 public JingleTransport getTransport() {
884 return this.transport;
885 }
886
887 public boolean start() {
888 if (account.getStatus() == Account.STATUS_ONLINE) {
889 if (mJingleStatus == JINGLE_STATUS_INITIATED) {
890 new Thread(new Runnable() {
891
892 @Override
893 public void run() {
894 sendAccept();
895 }
896 }).start();
897 }
898 return true;
899 } else {
900 return false;
901 }
902 }
903
904 @Override
905 public int getStatus() {
906 return this.mStatus;
907 }
908
909 @Override
910 public long getFileSize() {
911 if (this.file != null) {
912 return this.file.getExpectedSize();
913 } else {
914 return 0;
915 }
916 }
917
918 @Override
919 public int getProgress() {
920 return this.mProgress;
921 }
922
923 @Override
924 public String getMimeType() {
925 return this.file.getMimeType();
926 }
927}