JingleConnection.java

  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}