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