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