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 String getAccountJid() {
154		return this.account.getFullJid();
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.getMessages().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							.getConversationsFile(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()
362					.getConversationsFile(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		JinglePacket packet = this.bootstrapPacket("transport-replace");
638		Content content = new Content(this.contentCreator, this.contentName);
639		this.transportId = this.mJingleConnectionManager.nextRandomId();
640		content.setTransportId(this.transportId);
641		content.ibbTransport().setAttribute("block-size",
642				Integer.toString(this.ibbBlockSize));
643		packet.setContent(content);
644		this.sendJinglePacket(packet);
645	}
646
647	private boolean receiveFallbackToIbb(JinglePacket packet) {
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				if (this.mJingleStatus == JINGLE_STATUS_INITIATED) {
718					this.mXmppConnectionService.markMessage(this.message,
719							Message.STATUS_SEND_REJECTED);
720				} else {
721					this.mXmppConnectionService.markMessage(this.message,
722							Message.STATUS_SEND_FAILED);
723				}
724			}
725		}
726		this.mJingleConnectionManager.finishConnection(this);
727	}
728
729	private void sendCancel() {
730		JinglePacket packet = bootstrapPacket("session-terminate");
731		Reason reason = new Reason();
732		reason.addChild("cancel");
733		packet.setReason(reason);
734		this.sendJinglePacket(packet);
735	}
736
737	private void connectNextCandidate() {
738		for (JingleCandidate candidate : this.candidates) {
739			if ((!connections.containsKey(candidate.getCid()) && (!candidate
740					.isOurs()))) {
741				this.connectWithCandidate(candidate);
742				return;
743			}
744		}
745		this.sendCandidateError();
746	}
747
748	private void connectWithCandidate(final JingleCandidate candidate) {
749		final JingleSocks5Transport socksConnection = new JingleSocks5Transport(
750				this, candidate);
751		connections.put(candidate.getCid(), socksConnection);
752		socksConnection.connect(new OnTransportConnected() {
753
754			@Override
755			public void failed() {
756				Log.d(Config.LOGTAG,
757						"connection failed with " + candidate.getHost() + ":"
758								+ candidate.getPort());
759				connectNextCandidate();
760			}
761
762			@Override
763			public void established() {
764				Log.d(Config.LOGTAG,
765						"established connection with " + candidate.getHost()
766								+ ":" + candidate.getPort());
767				sendCandidateUsed(candidate.getCid());
768			}
769		});
770	}
771
772	private void disconnect() {
773		Iterator<Entry<String, JingleSocks5Transport>> it = this.connections
774				.entrySet().iterator();
775		while (it.hasNext()) {
776			Entry<String, JingleSocks5Transport> pairs = it.next();
777			pairs.getValue().disconnect();
778			it.remove();
779		}
780	}
781
782	private void sendProxyActivated(String cid) {
783		JinglePacket packet = bootstrapPacket("transport-info");
784		Content content = new Content(this.contentCreator, this.contentName);
785		content.setTransportId(this.transportId);
786		content.socks5transport().addChild("activated")
787				.setAttribute("cid", cid);
788		packet.setContent(content);
789		this.sendJinglePacket(packet);
790	}
791
792	private void sendCandidateUsed(final String cid) {
793		JinglePacket packet = bootstrapPacket("transport-info");
794		Content content = new Content(this.contentCreator, this.contentName);
795		content.setTransportId(this.transportId);
796		content.socks5transport().addChild("candidate-used")
797				.setAttribute("cid", cid);
798		packet.setContent(content);
799		this.sentCandidate = true;
800		if ((receivedCandidate) && (mJingleStatus == JINGLE_STATUS_ACCEPTED)) {
801			connect();
802		}
803		this.sendJinglePacket(packet);
804	}
805
806	private void sendCandidateError() {
807		JinglePacket packet = bootstrapPacket("transport-info");
808		Content content = new Content(this.contentCreator, this.contentName);
809		content.setTransportId(this.transportId);
810		content.socks5transport().addChild("candidate-error");
811		packet.setContent(content);
812		this.sentCandidate = true;
813		if ((receivedCandidate) && (mJingleStatus == JINGLE_STATUS_ACCEPTED)) {
814			connect();
815		}
816		this.sendJinglePacket(packet);
817	}
818
819	public String getInitiator() {
820		return this.initiator;
821	}
822
823	public String getResponder() {
824		return this.responder;
825	}
826
827	public int getJingleStatus() {
828		return this.mJingleStatus;
829	}
830
831	private boolean equalCandidateExists(JingleCandidate candidate) {
832		for (JingleCandidate c : this.candidates) {
833			if (c.equalValues(candidate)) {
834				return true;
835			}
836		}
837		return false;
838	}
839
840	private void mergeCandidate(JingleCandidate candidate) {
841		for (JingleCandidate c : this.candidates) {
842			if (c.equals(candidate)) {
843				return;
844			}
845		}
846		this.candidates.add(candidate);
847	}
848
849	private void mergeCandidates(List<JingleCandidate> candidates) {
850		for (JingleCandidate c : candidates) {
851			mergeCandidate(c);
852		}
853	}
854
855	private JingleCandidate getCandidate(String cid) {
856		for (JingleCandidate c : this.candidates) {
857			if (c.getCid().equals(cid)) {
858				return c;
859			}
860		}
861		return null;
862	}
863
864	interface OnProxyActivated {
865		public void success();
866
867		public void failed();
868	}
869
870	public boolean hasTransportId(String sid) {
871		return sid.equals(this.transportId);
872	}
873
874	public JingleTransport getTransport() {
875		return this.transport;
876	}
877
878	public void start() {
879		if (mJingleStatus == JINGLE_STATUS_INITIATED) {
880			new Thread(new Runnable() {
881
882				@Override
883				public void run() {
884					sendAccept();
885				}
886			}).start();
887		} else {
888			Log.d(Config.LOGTAG, "status (" + mJingleStatus + ") was not ok");
889		}
890	}
891
892	@Override
893	public int getStatus() {
894		return this.mStatus;
895	}
896
897	@Override
898	public long getFileSize() {
899		if (this.file != null) {
900			return this.file.getExpectedSize();
901		} else {
902			return 0;
903		}
904	}
905}