1package eu.siacs.conversations.xmpp.jingle;
2
3import java.util.ArrayList;
4import java.util.Arrays;
5import java.util.HashMap;
6import java.util.Iterator;
7import java.util.List;
8import java.util.Map.Entry;
9
10import android.graphics.BitmapFactory;
11import android.util.Log;
12
13import eu.siacs.conversations.entities.Account;
14import eu.siacs.conversations.entities.Conversation;
15import eu.siacs.conversations.entities.Message;
16import eu.siacs.conversations.services.XmppConnectionService;
17import eu.siacs.conversations.xml.Element;
18import eu.siacs.conversations.xmpp.OnIqPacketReceived;
19import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
20import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
21import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
22import eu.siacs.conversations.xmpp.stanzas.IqPacket;
23
24public class JingleConnection {
25
26 private final String[] extensions = {"webp","jpeg","jpg","png"};
27 private final String[] cryptoExtensions = {"pgp","gpg"};
28
29 private JingleConnectionManager mJingleConnectionManager;
30 private XmppConnectionService mXmppConnectionService;
31
32 public static final int STATUS_INITIATED = 0;
33 public static final int STATUS_ACCEPTED = 1;
34 public static final int STATUS_TERMINATED = 2;
35 public static final int STATUS_CANCELED = 3;
36 public static final int STATUS_FINISHED = 4;
37 public static final int STATUS_TRANSMITTING = 5;
38 public static final int STATUS_FAILED = 99;
39
40 private int ibbBlockSize = 4096;
41
42 private int status = -1;
43 private Message message;
44 private String sessionId;
45 private Account account;
46 private String initiator;
47 private String responder;
48 private List<JingleCandidate> candidates = new ArrayList<JingleCandidate>();
49 private HashMap<String, JingleSocks5Transport> connections = new HashMap<String, JingleSocks5Transport>();
50
51 private String transportId;
52 private Element fileOffer;
53 private JingleFile file = null;
54
55 private String contentName;
56 private String contentCreator;
57
58 private boolean receivedCandidate = false;
59 private boolean sentCandidate = false;
60
61 private boolean acceptedAutomatically = false;
62
63 private JingleTransport transport = null;
64
65 private OnIqPacketReceived responseListener = new OnIqPacketReceived() {
66
67 @Override
68 public void onIqPacketReceived(Account account, IqPacket packet) {
69 if (packet.getType() == IqPacket.TYPE_ERROR) {
70 if (initiator.equals(account.getFullJid())) {
71 mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
72 }
73 status = STATUS_FAILED;
74 }
75 }
76 };
77
78 final OnFileTransmitted onFileTransmitted = new OnFileTransmitted() {
79
80 @Override
81 public void onFileTransmitted(JingleFile file) {
82 if (responder.equals(account.getFullJid())) {
83 sendSuccess();
84 if (acceptedAutomatically) {
85 message.markUnread();
86 JingleConnection.this.mXmppConnectionService.updateUi(message.getConversation(), true);
87 }
88 BitmapFactory.Options options = new BitmapFactory.Options();
89 options.inJustDecodeBounds = true;
90 BitmapFactory.decodeFile(file.getAbsolutePath(),options);
91 int imageHeight = options.outHeight;
92 int imageWidth = options.outWidth;
93 message.setBody(""+file.getSize()+","+imageWidth+","+imageHeight);
94 mXmppConnectionService.databaseBackend.createMessage(message);
95 mXmppConnectionService.markMessage(message, Message.STATUS_RECIEVED);
96 }
97 Log.d("xmppService","sucessfully transmitted file:"+file.getName()+" encryption:"+message.getEncryption());
98 }
99 };
100
101 private OnProxyActivated onProxyActivated = new OnProxyActivated() {
102
103 @Override
104 public void success() {
105 if (initiator.equals(account.getFullJid())) {
106 Log.d("xmppService","we were initiating. sending file");
107 transport.send(file,onFileTransmitted);
108 } else {
109 transport.receive(file,onFileTransmitted);
110 Log.d("xmppService","we were responding. receiving file");
111 }
112 }
113
114 @Override
115 public void failed() {
116 Log.d("xmppService","proxy activation failed");
117 }
118 };
119
120 public JingleConnection(JingleConnectionManager mJingleConnectionManager) {
121 this.mJingleConnectionManager = mJingleConnectionManager;
122 this.mXmppConnectionService = mJingleConnectionManager.getXmppConnectionService();
123 }
124
125 public String getSessionId() {
126 return this.sessionId;
127 }
128
129 public String getAccountJid() {
130 return this.account.getFullJid();
131 }
132
133 public String getCounterPart() {
134 return this.message.getCounterpart();
135 }
136
137 public void deliverPacket(JinglePacket packet) {
138
139 if (packet.isAction("session-terminate")) {
140 Reason reason = packet.getReason();
141 if (reason!=null) {
142 if (reason.hasChild("cancel")) {
143 this.receiveCancel();
144 } else if (reason.hasChild("success")) {
145 this.receiveSuccess();
146 }
147 } else {
148 Log.d("xmppService","remote terminated for no reason");
149 this.receiveCancel();
150 }
151 } else if (packet.isAction("session-accept")) {
152 receiveAccept(packet);
153 } else if (packet.isAction("transport-info")) {
154 receiveTransportInfo(packet);
155 } else if (packet.isAction("transport-replace")) {
156 if (packet.getJingleContent().hasIbbTransport()) {
157 this.receiveFallbackToIbb(packet);
158 } else {
159 Log.d("xmppService","trying to fallback to something unknown"+packet.toString());
160 }
161 } else if (packet.isAction("transport-accept")) {
162 this.receiveTransportAccept(packet);
163 } else {
164 Log.d("xmppService","packet arrived in connection. action was "+packet.getAction());
165 }
166 }
167
168 public void init(Message message) {
169 this.contentCreator = "initiator";
170 this.contentName = this.mJingleConnectionManager.nextRandomId();
171 this.message = message;
172 this.account = message.getConversation().getAccount();
173 this.initiator = this.account.getFullJid();
174 this.responder = this.message.getCounterpart();
175 this.sessionId = this.mJingleConnectionManager.nextRandomId();
176 if (this.candidates.size() > 0) {
177 this.sendInitRequest();
178 } else {
179 this.mJingleConnectionManager.getPrimaryCandidate(account, new OnPrimaryCandidateFound() {
180
181 @Override
182 public void onPrimaryCandidateFound(boolean success, final JingleCandidate candidate) {
183 if (success) {
184 final JingleSocks5Transport socksConnection = new JingleSocks5Transport(JingleConnection.this, candidate);
185 connections.put(candidate.getCid(), socksConnection);
186 socksConnection.connect(new OnTransportConnected() {
187
188 @Override
189 public void failed() {
190 Log.d("xmppService","connection to our own primary candidete failed");
191 sendInitRequest();
192 }
193
194 @Override
195 public void established() {
196 Log.d("xmppService","succesfully connected to our own primary candidate");
197 mergeCandidate(candidate);
198 sendInitRequest();
199 }
200 });
201 mergeCandidate(candidate);
202 } else {
203 Log.d("xmppService","no primary candidate of our own was found");
204 sendInitRequest();
205 }
206 }
207 });
208 }
209
210 }
211
212 public void init(Account account, JinglePacket packet) {
213 this.status = STATUS_INITIATED;
214 Conversation conversation = this.mXmppConnectionService.findOrCreateConversation(account, packet.getFrom().split("/")[0], false);
215 this.message = new Message(conversation, "", Message.ENCRYPTION_NONE);
216 this.message.setType(Message.TYPE_IMAGE);
217 this.message.setStatus(Message.STATUS_RECEIVED_OFFER);
218 this.message.setJingleConnection(this);
219 String[] fromParts = packet.getFrom().split("/");
220 this.message.setPresence(fromParts[1]);
221 this.account = account;
222 this.initiator = packet.getFrom();
223 this.responder = this.account.getFullJid();
224 this.sessionId = packet.getSessionId();
225 Content content = packet.getJingleContent();
226 this.contentCreator = content.getAttribute("creator");
227 this.contentName = content.getAttribute("name");
228 this.transportId = content.getTransportId();
229 this.mergeCandidates(JingleCandidate.parse(content.socks5transport().getChildren()));
230 this.fileOffer = packet.getJingleContent().getFileOffer();
231 if (fileOffer!=null) {
232 Element fileSize = fileOffer.findChild("size");
233 Element fileNameElement = fileOffer.findChild("name");
234 if (fileNameElement!=null) {
235 boolean supportedFile = false;
236 String[] filename = fileNameElement.getContent().toLowerCase().split("\\.");
237 if (Arrays.asList(this.extensions).contains(filename[filename.length - 1])) {
238 supportedFile = true;
239 } else if (Arrays.asList(this.cryptoExtensions).contains(filename[filename.length - 1])) {
240 if (filename.length == 3) {
241 if (Arrays.asList(this.extensions).contains(filename[filename.length -2])) {
242 supportedFile = true;
243 this.message.setEncryption(Message.ENCRYPTION_PGP);
244 }
245 }
246 }
247 if (supportedFile) {
248 long size = Long.parseLong(fileSize.getContent());
249 message.setBody(""+size);
250 conversation.getMessages().add(message);
251 if (size<=this.mJingleConnectionManager.getAutoAcceptFileSize()) {
252 Log.d("xmppService","auto accepting file from "+packet.getFrom());
253 this.acceptedAutomatically = true;
254 this.sendAccept();
255 } else {
256 message.markUnread();
257 Log.d("xmppService","not auto accepting new file offer with size: "+size+" allowed size:"+this.mJingleConnectionManager.getAutoAcceptFileSize());
258 this.mXmppConnectionService.updateUi(conversation, true);
259 }
260 this.file = this.mXmppConnectionService.getFileBackend().getJingleFile(message,false);
261 this.file.setExpectedSize(size);
262 } else {
263 this.sendCancel();
264 }
265 } else {
266 this.sendCancel();
267 }
268 } else {
269 this.sendCancel();
270 }
271 }
272
273 private void sendInitRequest() {
274 JinglePacket packet = this.bootstrapPacket("session-initiate");
275 Content content = new Content(this.contentCreator,this.contentName);
276 if (message.getType() == Message.TYPE_IMAGE) {
277 content.setTransportId(this.transportId);
278 this.file = this.mXmppConnectionService.getFileBackend().getJingleFile(message,false);
279 content.setFileOffer(this.file);
280 this.transportId = this.mJingleConnectionManager.nextRandomId();
281 content.setTransportId(this.transportId);
282 content.socks5transport().setChildren(getCandidatesAsElements());
283 packet.setContent(content);
284 this.sendJinglePacket(packet);
285 this.status = STATUS_INITIATED;
286 }
287 }
288
289 private List<Element> getCandidatesAsElements() {
290 List<Element> elements = new ArrayList<Element>();
291 for(JingleCandidate c : this.candidates) {
292 elements.add(c.toElement());
293 }
294 return elements;
295 }
296
297 private void sendAccept() {
298 status = STATUS_ACCEPTED;
299 mXmppConnectionService.markMessage(message, Message.STATUS_RECIEVING);
300 this.mJingleConnectionManager.getPrimaryCandidate(this.account, new OnPrimaryCandidateFound() {
301
302 @Override
303 public void onPrimaryCandidateFound(boolean success,final JingleCandidate candidate) {
304 final JinglePacket packet = bootstrapPacket("session-accept");
305 final Content content = new Content(contentCreator,contentName);
306 content.setFileOffer(fileOffer);
307 content.setTransportId(transportId);
308 if ((success)&&(!equalCandidateExists(candidate))) {
309 final JingleSocks5Transport socksConnection = new JingleSocks5Transport(JingleConnection.this, candidate);
310 connections.put(candidate.getCid(), socksConnection);
311 socksConnection.connect(new OnTransportConnected() {
312
313 @Override
314 public void failed() {
315 Log.d("xmppService","connection to our own primary candidate failed");
316 content.socks5transport().setChildren(getCandidatesAsElements());
317 packet.setContent(content);
318 sendJinglePacket(packet);
319 connectNextCandidate();
320 }
321
322 @Override
323 public void established() {
324 Log.d("xmppService","connected to primary candidate");
325 mergeCandidate(candidate);
326 content.socks5transport().setChildren(getCandidatesAsElements());
327 packet.setContent(content);
328 sendJinglePacket(packet);
329 connectNextCandidate();
330 }
331 });
332 } else {
333 Log.d("xmppService","did not find a primary candidate for ourself");
334 content.socks5transport().setChildren(getCandidatesAsElements());
335 packet.setContent(content);
336 sendJinglePacket(packet);
337 connectNextCandidate();
338 }
339 }
340 });
341
342 }
343
344 private JinglePacket bootstrapPacket(String action) {
345 JinglePacket packet = new JinglePacket();
346 packet.setAction(action);
347 packet.setFrom(account.getFullJid());
348 packet.setTo(this.message.getCounterpart());
349 packet.setSessionId(this.sessionId);
350 packet.setInitiator(this.initiator);
351 return packet;
352 }
353
354 private void sendJinglePacket(JinglePacket packet) {
355 //Log.d("xmppService",packet.toString());
356 account.getXmppConnection().sendIqPacket(packet,responseListener);
357 }
358
359 private void receiveAccept(JinglePacket packet) {
360 Content content = packet.getJingleContent();
361 mergeCandidates(JingleCandidate.parse(content.socks5transport().getChildren()));
362 this.status = STATUS_ACCEPTED;
363 mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND);
364 this.connectNextCandidate();
365 IqPacket response = packet.generateRespone(IqPacket.TYPE_RESULT);
366 account.getXmppConnection().sendIqPacket(response, null);
367 }
368
369 private void receiveTransportInfo(JinglePacket packet) {
370 Content content = packet.getJingleContent();
371 if (content.hasSocks5Transport()) {
372 if (content.socks5transport().hasChild("activated")) {
373 if ((this.transport!=null)&&(this.transport instanceof JingleSocks5Transport)) {
374 onProxyActivated.success();
375 } else {
376 String cid = content.socks5transport().findChild("activated").getAttribute("cid");
377 Log.d("xmppService","received proxy activated ("+cid+")prior to choosing our own transport");
378 JingleSocks5Transport connection = this.connections.get(cid);
379 if (connection!=null) {
380 connection.setActivated(true);
381 } else {
382 Log.d("xmppService","activated connection not found");
383 this.sendCancel();
384 }
385 }
386 } else if (content.socks5transport().hasChild("activated")) {
387 onProxyActivated.failed();
388 } else if (content.socks5transport().hasChild("candidate-error")) {
389 Log.d("xmppService","received candidate error");
390 this.receivedCandidate = true;
391 if ((status == STATUS_ACCEPTED)&&(this.sentCandidate)) {
392 this.connect();
393 }
394 } else if (content.socks5transport().hasChild("candidate-used")){
395 String cid = content.socks5transport().findChild("candidate-used").getAttribute("cid");
396 if (cid!=null) {
397 Log.d("xmppService","candidate used by counterpart:"+cid);
398 JingleCandidate candidate = getCandidate(cid);
399 candidate.flagAsUsedByCounterpart();
400 this.receivedCandidate = true;
401 if ((status == STATUS_ACCEPTED)&&(this.sentCandidate)) {
402 this.connect();
403 } else {
404 Log.d("xmppService","ignoring because file is already in transmission or we havent sent our candidate yet");
405 }
406 } else {
407 Log.d("xmppService","couldn't read used candidate");
408 }
409 } else {
410 Log.d("xmppService","empty transport");
411 }
412 }
413
414 IqPacket response = packet.generateRespone(IqPacket.TYPE_RESULT);
415 account.getXmppConnection().sendIqPacket(response, null);
416 }
417
418 private void connect() {
419 final JingleSocks5Transport connection = chooseConnection();
420 this.transport = connection;
421 if (connection==null) {
422 Log.d("xmppService","could not find suitable candidate");
423 this.disconnect();
424 if (this.initiator.equals(account.getFullJid())) {
425 this.sendFallbackToIbb();
426 }
427 } else {
428 this.status = STATUS_TRANSMITTING;
429 if (connection.needsActivation()) {
430 if (connection.getCandidate().isOurs()) {
431 Log.d("xmppService","candidate "+connection.getCandidate().getCid()+" was our proxy. going to activate");
432 IqPacket activation = new IqPacket(IqPacket.TYPE_SET);
433 activation.setTo(connection.getCandidate().getJid());
434 activation.query("http://jabber.org/protocol/bytestreams").setAttribute("sid", this.getSessionId());
435 activation.query().addChild("activate").setContent(this.getCounterPart());
436 this.account.getXmppConnection().sendIqPacket(activation, new OnIqPacketReceived() {
437
438 @Override
439 public void onIqPacketReceived(Account account, IqPacket packet) {
440 if (packet.getType()==IqPacket.TYPE_ERROR) {
441 onProxyActivated.failed();
442 } else {
443 onProxyActivated.success();
444 sendProxyActivated(connection.getCandidate().getCid());
445 }
446 }
447 });
448 } else {
449 Log.d("xmppService","candidate "+connection.getCandidate().getCid()+" was a proxy. waiting for other party to activate");
450 }
451 } else {
452 if (initiator.equals(account.getFullJid())) {
453 Log.d("xmppService","we were initiating. sending file");
454 connection.send(file,onFileTransmitted);
455 } else {
456 Log.d("xmppService","we were responding. receiving file");
457 connection.receive(file,onFileTransmitted);
458 }
459 }
460 }
461 }
462
463 private JingleSocks5Transport chooseConnection() {
464 JingleSocks5Transport connection = null;
465 Iterator<Entry<String, JingleSocks5Transport>> it = this.connections.entrySet().iterator();
466 while (it.hasNext()) {
467 Entry<String, JingleSocks5Transport> pairs = it.next();
468 JingleSocks5Transport currentConnection = pairs.getValue();
469 //Log.d("xmppService","comparing candidate: "+currentConnection.getCandidate().toString());
470 if (currentConnection.isEstablished()&&(currentConnection.getCandidate().isUsedByCounterpart()||(!currentConnection.getCandidate().isOurs()))) {
471 //Log.d("xmppService","is usable");
472 if (connection==null) {
473 connection = currentConnection;
474 } else {
475 if (connection.getCandidate().getPriority()<currentConnection.getCandidate().getPriority()) {
476 connection = currentConnection;
477 } else if (connection.getCandidate().getPriority()==currentConnection.getCandidate().getPriority()) {
478 //Log.d("xmppService","found two candidates with same priority");
479 if (initiator.equals(account.getFullJid())) {
480 if (currentConnection.getCandidate().isOurs()) {
481 connection = currentConnection;
482 }
483 } else {
484 if (!currentConnection.getCandidate().isOurs()) {
485 connection = currentConnection;
486 }
487 }
488 }
489 }
490 }
491 it.remove();
492 }
493 return connection;
494 }
495
496 private void sendSuccess() {
497 JinglePacket packet = bootstrapPacket("session-terminate");
498 Reason reason = new Reason();
499 reason.addChild("success");
500 packet.setReason(reason);
501 this.sendJinglePacket(packet);
502 this.disconnect();
503 this.status = STATUS_FINISHED;
504 this.mXmppConnectionService.markMessage(this.message, Message.STATUS_RECIEVED);
505 }
506
507 private void sendFallbackToIbb() {
508 JinglePacket packet = this.bootstrapPacket("transport-replace");
509 Content content = new Content(this.contentCreator,this.contentName);
510 this.transportId = this.mJingleConnectionManager.nextRandomId();
511 content.setTransportId(this.transportId);
512 content.ibbTransport().setAttribute("block-size",""+this.ibbBlockSize);
513 packet.setContent(content);
514 this.sendJinglePacket(packet);
515 }
516
517 private void receiveFallbackToIbb(JinglePacket packet) {
518 String receivedBlockSize = packet.getJingleContent().ibbTransport().getAttribute("block-size");
519 if (receivedBlockSize!=null) {
520 int bs = Integer.parseInt(receivedBlockSize);
521 if (bs>this.ibbBlockSize) {
522 this.ibbBlockSize = bs;
523 }
524 }
525 this.transportId = packet.getJingleContent().getTransportId();
526 this.transport = new JingleInbandTransport(this.account,this.responder,this.transportId,this.ibbBlockSize);
527 this.transport.receive(file, onFileTransmitted);
528 JinglePacket answer = bootstrapPacket("transport-accept");
529 Content content = new Content("initiator", "a-file-offer");
530 content.setTransportId(this.transportId);
531 content.ibbTransport().setAttribute("block-size", ""+this.ibbBlockSize);
532 answer.setContent(content);
533 this.sendJinglePacket(answer);
534 }
535
536 private void receiveTransportAccept(JinglePacket packet) {
537 if (packet.getJingleContent().hasIbbTransport()) {
538 String receivedBlockSize = packet.getJingleContent().ibbTransport().getAttribute("block-size");
539 if (receivedBlockSize!=null) {
540 int bs = Integer.parseInt(receivedBlockSize);
541 if (bs>this.ibbBlockSize) {
542 this.ibbBlockSize = bs;
543 }
544 }
545 this.transport = new JingleInbandTransport(this.account,this.responder,this.transportId,this.ibbBlockSize);
546 this.transport.connect(new OnTransportConnected() {
547
548 @Override
549 public void failed() {
550 Log.d("xmppService","ibb open failed");
551 }
552
553 @Override
554 public void established() {
555 JingleConnection.this.transport.send(file, onFileTransmitted);
556 }
557 });
558 } else {
559 Log.d("xmppService","invalid transport accept");
560 }
561 }
562
563 private void receiveSuccess() {
564 this.status = STATUS_FINISHED;
565 this.mXmppConnectionService.markMessage(this.message, Message.STATUS_SEND);
566 this.disconnect();
567 }
568
569 private void receiveCancel() {
570 this.disconnect();
571 this.status = STATUS_CANCELED;
572 this.mXmppConnectionService.markMessage(this.message, Message.STATUS_SEND_REJECTED);
573 }
574
575 private void sendCancel() {
576 JinglePacket packet = bootstrapPacket("session-terminate");
577 Reason reason = new Reason();
578 reason.addChild("cancel");
579 packet.setReason(reason);
580 this.sendJinglePacket(packet);
581 }
582
583 private void connectNextCandidate() {
584 for(JingleCandidate candidate : this.candidates) {
585 if ((!connections.containsKey(candidate.getCid())&&(!candidate.isOurs()))) {
586 this.connectWithCandidate(candidate);
587 return;
588 }
589 }
590 this.sendCandidateError();
591 }
592
593 private void connectWithCandidate(final JingleCandidate candidate) {
594 final JingleSocks5Transport socksConnection = new JingleSocks5Transport(this,candidate);
595 connections.put(candidate.getCid(), socksConnection);
596 socksConnection.connect(new OnTransportConnected() {
597
598 @Override
599 public void failed() {
600 Log.d("xmppService", "connection failed with "+candidate.getHost()+":"+candidate.getPort());
601 connectNextCandidate();
602 }
603
604 @Override
605 public void established() {
606 Log.d("xmppService", "established connection with "+candidate.getHost()+":"+candidate.getPort());
607 sendCandidateUsed(candidate.getCid());
608 }
609 });
610 }
611
612 private void disconnect() {
613 Iterator<Entry<String, JingleSocks5Transport>> it = this.connections.entrySet().iterator();
614 while (it.hasNext()) {
615 Entry<String, JingleSocks5Transport> pairs = it.next();
616 pairs.getValue().disconnect();
617 it.remove();
618 }
619 }
620
621 private void sendProxyActivated(String cid) {
622 JinglePacket packet = bootstrapPacket("transport-info");
623 Content content = new Content(this.contentCreator,this.contentName);
624 content.setTransportId(this.transportId);
625 content.socks5transport().addChild("activated").setAttribute("cid", cid);
626 packet.setContent(content);
627 this.sendJinglePacket(packet);
628 }
629
630 private void sendCandidateUsed(final String cid) {
631 JinglePacket packet = bootstrapPacket("transport-info");
632 Content content = new Content(this.contentCreator,this.contentName);
633 content.setTransportId(this.transportId);
634 content.socks5transport().addChild("candidate-used").setAttribute("cid", cid);
635 packet.setContent(content);
636 this.sentCandidate = true;
637 if ((receivedCandidate)&&(status == STATUS_ACCEPTED)) {
638 connect();
639 }
640 this.sendJinglePacket(packet);
641 }
642
643 private void sendCandidateError() {
644 JinglePacket packet = bootstrapPacket("transport-info");
645 Content content = new Content(this.contentCreator,this.contentName);
646 content.setTransportId(this.transportId);
647 content.socks5transport().addChild("candidate-error");
648 packet.setContent(content);
649 this.sentCandidate = true;
650 if ((receivedCandidate)&&(status == STATUS_ACCEPTED)) {
651 connect();
652 }
653 this.sendJinglePacket(packet);
654 }
655
656 public String getInitiator() {
657 return this.initiator;
658 }
659
660 public String getResponder() {
661 return this.responder;
662 }
663
664 public int getStatus() {
665 return this.status;
666 }
667
668 private boolean equalCandidateExists(JingleCandidate candidate) {
669 for(JingleCandidate c : this.candidates) {
670 if (c.equalValues(candidate)) {
671 return true;
672 }
673 }
674 return false;
675 }
676
677 private void mergeCandidate(JingleCandidate candidate) {
678 for(JingleCandidate c : this.candidates) {
679 if (c.equals(candidate)) {
680 return;
681 }
682 }
683 this.candidates.add(candidate);
684 }
685
686 private void mergeCandidates(List<JingleCandidate> candidates) {
687 for(JingleCandidate c : candidates) {
688 mergeCandidate(c);
689 }
690 }
691
692 private JingleCandidate getCandidate(String cid) {
693 for(JingleCandidate c : this.candidates) {
694 if (c.getCid().equals(cid)) {
695 return c;
696 }
697 }
698 return null;
699 }
700
701 interface OnProxyActivated {
702 public void success();
703 public void failed();
704 }
705
706 public boolean hasTransportId(String sid) {
707 return sid.equals(this.transportId);
708 }
709
710 public JingleTransport getTransport() {
711 return this.transport;
712 }
713
714 public void accept() {
715 if (status==STATUS_INITIATED) {
716 new Thread(new Runnable() {
717
718 @Override
719 public void run() {
720 sendAccept();
721 }
722 }).start();
723 } else {
724 Log.d("xmppService","status ("+status+") was not ok");
725 }
726 }
727}