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