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.os.SystemClock;
 15import android.util.Log;
 16import eu.siacs.conversations.Config;
 17import eu.siacs.conversations.entities.Account;
 18import eu.siacs.conversations.entities.Conversation;
 19import eu.siacs.conversations.entities.Downloadable;
 20import eu.siacs.conversations.entities.DownloadableFile;
 21import eu.siacs.conversations.entities.Message;
 22import eu.siacs.conversations.services.XmppConnectionService;
 23import eu.siacs.conversations.xml.Element;
 24import eu.siacs.conversations.xmpp.OnIqPacketReceived;
 25import eu.siacs.conversations.xmpp.jid.Jid;
 26import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
 27import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
 28import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
 29import eu.siacs.conversations.xmpp.stanzas.IqPacket;
 30
 31public class JingleConnection implements Downloadable {
 32
 33	private JingleConnectionManager mJingleConnectionManager;
 34	private XmppConnectionService mXmppConnectionService;
 35
 36	protected static final int JINGLE_STATUS_INITIATED = 0;
 37	protected static final int JINGLE_STATUS_ACCEPTED = 1;
 38	protected static final int JINGLE_STATUS_TERMINATED = 2;
 39	protected static final int JINGLE_STATUS_CANCELED = 3;
 40	protected static final int JINGLE_STATUS_FINISHED = 4;
 41	protected static final int JINGLE_STATUS_TRANSMITTING = 5;
 42	protected static final int JINGLE_STATUS_FAILED = 99;
 43
 44	private int ibbBlockSize = 4096;
 45
 46	private int mJingleStatus = -1;
 47	private int mStatus = -1;
 48	private Message message;
 49	private String sessionId;
 50	private Account account;
 51	private Jid initiator;
 52	private Jid responder;
 53	private List<JingleCandidate> candidates = new ArrayList<>();
 54	private ConcurrentHashMap<String, JingleSocks5Transport> connections = new ConcurrentHashMap<>();
 55
 56	private String transportId;
 57	private Element fileOffer;
 58	private DownloadableFile file = null;
 59
 60	private String contentName;
 61	private String contentCreator;
 62
 63	private int mProgress = 0;
 64	private long mLastGuiRefresh = 0;
 65
 66	private boolean receivedCandidate = false;
 67	private boolean sentCandidate = false;
 68
 69	private boolean acceptedAutomatically = false;
 70
 71	private JingleTransport transport = null;
 72
 73	private OnIqPacketReceived responseListener = new OnIqPacketReceived() {
 74
 75		@Override
 76		public void onIqPacketReceived(Account account, IqPacket packet) {
 77			if (packet.getType() == IqPacket.TYPE_ERROR) {
 78				cancel();
 79			}
 80		}
 81	};
 82
 83	final OnFileTransmissionStatusChanged onFileTransmissionSatusChanged = new OnFileTransmissionStatusChanged() {
 84
 85		@Override
 86		public void onFileTransmitted(DownloadableFile file) {
 87			if (responder.equals(account.getJid())) {
 88				sendSuccess();
 89				if (acceptedAutomatically) {
 90					message.markUnread();
 91					JingleConnection.this.mXmppConnectionService
 92							.getNotificationService().push(message);
 93				}
 94				mXmppConnectionService.getFileBackend().updateFileParams(message);
 95				mXmppConnectionService.databaseBackend.createMessage(message);
 96				mXmppConnectionService.markMessage(message,
 97						Message.STATUS_RECEIVED);
 98			}
 99			Log.d(Config.LOGTAG,
100					"sucessfully transmitted file:" + file.getAbsolutePath());
101			if (message.getEncryption() != Message.ENCRYPTION_PGP) {
102				Intent intent = new Intent(
103						Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
104				intent.setData(Uri.fromFile(file));
105				mXmppConnectionService.sendBroadcast(intent);
106			}
107		}
108
109		@Override
110		public void onFileTransferAborted() {
111			JingleConnection.this.sendCancel();
112			JingleConnection.this.cancel();
113		}
114	};
115
116	private OnProxyActivated onProxyActivated = new OnProxyActivated() {
117
118		@Override
119		public void success() {
120			if (initiator.equals(account.getJid())) {
121				Log.d(Config.LOGTAG, "we were initiating. sending file");
122				transport.send(file, onFileTransmissionSatusChanged);
123			} else {
124				transport.receive(file, onFileTransmissionSatusChanged);
125				Log.d(Config.LOGTAG, "we were responding. receiving file");
126			}
127		}
128
129		@Override
130		public void failed() {
131			Log.d(Config.LOGTAG, "proxy activation failed");
132		}
133	};
134
135	public JingleConnection(JingleConnectionManager mJingleConnectionManager) {
136		this.mJingleConnectionManager = mJingleConnectionManager;
137		this.mXmppConnectionService = mJingleConnectionManager
138				.getXmppConnectionService();
139	}
140
141	public String getSessionId() {
142		return this.sessionId;
143	}
144
145	public Account getAccount() {
146		return this.account;
147	}
148
149	public Jid getCounterPart() {
150		return this.message.getCounterpart();
151	}
152
153	public void deliverPacket(JinglePacket packet) {
154		boolean returnResult = true;
155		if (packet.isAction("session-terminate")) {
156			Reason reason = packet.getReason();
157			if (reason != null) {
158				if (reason.hasChild("cancel")) {
159					this.cancel();
160				} else if (reason.hasChild("success")) {
161					this.receiveSuccess();
162				} else {
163					this.cancel();
164				}
165			} else {
166				this.cancel();
167			}
168		} else if (packet.isAction("session-accept")) {
169			returnResult = receiveAccept(packet);
170		} else if (packet.isAction("transport-info")) {
171			returnResult = receiveTransportInfo(packet);
172		} else if (packet.isAction("transport-replace")) {
173			if (packet.getJingleContent().hasIbbTransport()) {
174				returnResult = this.receiveFallbackToIbb(packet);
175			} else {
176				returnResult = false;
177				Log.d(Config.LOGTAG, "trying to fallback to something unknown"
178						+ packet.toString());
179			}
180		} else if (packet.isAction("transport-accept")) {
181			returnResult = this.receiveTransportAccept(packet);
182		} else {
183			Log.d(Config.LOGTAG, "packet arrived in connection. action was "
184					+ packet.getAction());
185			returnResult = false;
186		}
187		IqPacket response;
188		if (returnResult) {
189			response = packet.generateRespone(IqPacket.TYPE_RESULT);
190
191		} else {
192			response = packet.generateRespone(IqPacket.TYPE_ERROR);
193		}
194		account.getXmppConnection().sendIqPacket(response, null);
195	}
196
197	public void init(Message message) {
198		this.contentCreator = "initiator";
199		this.contentName = this.mJingleConnectionManager.nextRandomId();
200		this.message = message;
201		this.account = message.getConversation().getAccount();
202		this.initiator = this.account.getJid();
203		this.responder = this.message.getCounterpart();
204		this.sessionId = this.mJingleConnectionManager.nextRandomId();
205		if (this.candidates.size() > 0) {
206			this.sendInitRequest();
207		} else {
208			this.mJingleConnectionManager.getPrimaryCandidate(account,
209					new OnPrimaryCandidateFound() {
210
211						@Override
212						public void onPrimaryCandidateFound(boolean success,
213								final JingleCandidate candidate) {
214							if (success) {
215								final JingleSocks5Transport socksConnection = new JingleSocks5Transport(
216										JingleConnection.this, candidate);
217								connections.put(candidate.getCid(),
218										socksConnection);
219								socksConnection
220										.connect(new OnTransportConnected() {
221
222											@Override
223											public void failed() {
224												Log.d(Config.LOGTAG,
225														"connection to our own primary candidete failed");
226												sendInitRequest();
227											}
228
229											@Override
230											public void established() {
231												Log.d(Config.LOGTAG,
232														"succesfully connected to our own primary candidate");
233												mergeCandidate(candidate);
234												sendInitRequest();
235											}
236										});
237								mergeCandidate(candidate);
238							} else {
239								Log.d(Config.LOGTAG,
240										"no primary candidate of our own was found");
241								sendInitRequest();
242							}
243						}
244					});
245		}
246
247	}
248
249	public void init(Account account, JinglePacket packet) {
250		this.mJingleStatus = JINGLE_STATUS_INITIATED;
251		Conversation conversation = this.mXmppConnectionService
252				.findOrCreateConversation(account,
253						packet.getFrom().toBareJid(), false);
254		this.message = new Message(conversation, "", Message.ENCRYPTION_NONE);
255		this.message.setStatus(Message.STATUS_RECEIVED);
256		this.mStatus = Downloadable.STATUS_OFFER;
257		this.message.setDownloadable(this);
258        final Jid from = packet.getFrom();
259		this.message.setCounterpart(from);
260		this.account = account;
261		this.initiator = packet.getFrom();
262		this.responder = this.account.getJid();
263		this.sessionId = packet.getSessionId();
264		Content content = packet.getJingleContent();
265		this.contentCreator = content.getAttribute("creator");
266		this.contentName = content.getAttribute("name");
267		this.transportId = content.getTransportId();
268		this.mergeCandidates(JingleCandidate.parse(content.socks5transport()
269				.getChildren()));
270		this.fileOffer = packet.getJingleContent().getFileOffer();
271		if (fileOffer != null) {
272			Element fileSize = fileOffer.findChild("size");
273			Element fileNameElement = fileOffer.findChild("name");
274			if (fileNameElement != null) {
275				String[] filename = fileNameElement.getContent()
276						.toLowerCase(Locale.US).split("\\.");
277				if (Arrays.asList(VALID_IMAGE_EXTENSIONS).contains(
278						filename[filename.length - 1])) {
279					message.setType(Message.TYPE_IMAGE);
280				} else if (Arrays.asList(VALID_CRYPTO_EXTENSIONS).contains(
281						filename[filename.length - 1])) {
282					if (filename.length == 3) {
283						if (Arrays.asList(VALID_IMAGE_EXTENSIONS).contains(
284								filename[filename.length - 2])) {
285							message.setType(Message.TYPE_IMAGE);
286						} else {
287							message.setType(Message.TYPE_FILE);
288						}
289						if (filename[filename.length - 1].equals("otr")) {
290							message.setEncryption(Message.ENCRYPTION_OTR);
291						} else {
292							message.setEncryption(Message.ENCRYPTION_PGP);
293						}
294					}
295				} else {
296					message.setType(Message.TYPE_FILE);
297				}
298				if (message.getType() == Message.TYPE_FILE) {
299					String suffix = "";
300					if (!fileNameElement.getContent().isEmpty()) {
301						String parts[] = fileNameElement.getContent().split("/");
302						suffix = parts[parts.length - 1];
303						if (message.getEncryption() == Message.ENCRYPTION_OTR  && suffix.endsWith(".otr")) {
304							suffix = suffix.substring(0,suffix.length() - 4);
305						}
306					}
307					message.setRelativeFilePath(message.getUuid()+"_"+suffix);
308				}
309				long size = Long.parseLong(fileSize.getContent());
310				message.setBody(Long.toString(size));
311				conversation.add(message);
312				mXmppConnectionService.updateConversationUi();
313				if (size <= this.mJingleConnectionManager
314						.getAutoAcceptFileSize()) {
315					Log.d(Config.LOGTAG, "auto accepting file from "
316							+ packet.getFrom());
317					this.acceptedAutomatically = true;
318					this.sendAccept();
319				} else {
320					message.markUnread();
321					Log.d(Config.LOGTAG,
322							"not auto accepting new file offer with size: "
323									+ size
324									+ " allowed size:"
325									+ this.mJingleConnectionManager
326											.getAutoAcceptFileSize());
327					this.mXmppConnectionService.getNotificationService()
328							.push(message);
329				}
330				this.file = this.mXmppConnectionService.getFileBackend()
331						.getFile(message, false);
332				if (message.getEncryption() == Message.ENCRYPTION_OTR) {
333					byte[] key = conversation.getSymmetricKey();
334					if (key == null) {
335						this.sendCancel();
336						this.cancel();
337						return;
338					} else {
339						this.file.setKey(key);
340					}
341				}
342				this.file.setExpectedSize(size);
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 || message.getType() == Message.TYPE_FILE) {
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.getJid());
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.getJid())) {
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.getJid())) {
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.getJid())) {
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.getJid())) {
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	public void updateProgress(int i) {
860		this.mProgress = i;
861		if (SystemClock.elapsedRealtime() - this.mLastGuiRefresh > 1000) {
862			this.mLastGuiRefresh = SystemClock.elapsedRealtime();
863			mXmppConnectionService.updateConversationUi();
864		}
865	}
866
867	interface OnProxyActivated {
868		public void success();
869
870		public void failed();
871	}
872
873	public boolean hasTransportId(String sid) {
874		return sid.equals(this.transportId);
875	}
876
877	public JingleTransport getTransport() {
878		return this.transport;
879	}
880
881	public boolean start() {
882		if (account.getStatus() == Account.STATUS_ONLINE) {
883			if (mJingleStatus == JINGLE_STATUS_INITIATED) {
884				new Thread(new Runnable() {
885
886					@Override
887					public void run() {
888						sendAccept();
889					}
890				}).start();
891			}
892			return true;
893		} else {
894			return false;
895		}
896	}
897
898	@Override
899	public int getStatus() {
900		return this.mStatus;
901	}
902
903	@Override
904	public long getFileSize() {
905		if (this.file != null) {
906			return this.file.getExpectedSize();
907		} else {
908			return 0;
909		}
910	}
911
912	@Override
913	public int getProgress() {
914		return this.mProgress;
915	}
916
917	@Override
918	public String getMimeType() {
919		return this.file.getMimeType();
920	}
921}