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