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