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