JingleConnection.java

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