Merge tag '2.5.12' into develop

This commit is contained in:
Martin/Geno 2019-10-10 14:37:39 +02:00
commit 72be751568
No known key found for this signature in database
GPG Key ID: 9D7D3C6BFF600C6A
49 changed files with 3627 additions and 3296 deletions

View File

@ -1,5 +1,9 @@
# Changelog # Changelog
### Version 2.5.12
* Jingle file transfer fixes
* Fixed OMEMO self healing (after backup restore) on servers w/o MAM
### Version 2.5.11 ### Version 2.5.11
* Fixed crash on Android <5.0 * Fixed crash on Android <5.0

View File

@ -84,8 +84,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 25 targetSdkVersion 25
versionCode 342 versionCode 346
versionName "2.5.11.1" versionName "2.5.12"
archivesBaseName += "-$versionName" archivesBaseName += "-$versionName"
applicationId "eu.sum7.conversations" applicationId "eu.sum7.conversations"
resValue "string", "applicationId", applicationId resValue "string", "applicationId", applicationId

View File

@ -41,7 +41,7 @@ public final class Config {
public static final String MAGIC_CREATE_DOMAIN = "chat.sum7.eu"; public static final String MAGIC_CREATE_DOMAIN = "chat.sum7.eu";
public static final String QUICKSY_DOMAIN = "quicksy.im"; public static final String QUICKSY_DOMAIN = "quicksy.im";
public static final String CHANNEL_DISCOVERY = "https://search.jabbercat.org"; public static final String CHANNEL_DISCOVERY = "https://search.jabber.network";
public static final boolean DISALLOW_REGISTRATION_IN_UI = false; //hide the register checkbox public static final boolean DISALLOW_REGISTRATION_IN_UI = false; //hide the register checkbox
@ -99,6 +99,7 @@ public final class Config {
public static final boolean OMEMO_PADDING = false; public static final boolean OMEMO_PADDING = false;
public static final boolean PUT_AUTH_TAG_INTO_KEY = true; public static final boolean PUT_AUTH_TAG_INTO_KEY = true;
public static final boolean USE_BOOKMARKS2 = false;
public static final boolean DISABLE_PROXY_LOOKUP = false; //useful to debug ibb public static final boolean DISABLE_PROXY_LOOKUP = false; //useful to debug ibb
public static final boolean USE_DIRECT_JINGLE_CANDIDATES = true; public static final boolean USE_DIRECT_JINGLE_CANDIDATES = true;

View File

@ -839,6 +839,13 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}); });
} }
public void deleteOmemoIdentity() {
final String node = AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId();
final IqPacket deleteBundleNode = mXmppConnectionService.getIqGenerator().deleteNode(node);
mXmppConnectionService.sendIqPacket(account, deleteBundleNode, null);
publishDeviceIdsAndRefineAccessModel(getOwnDeviceIds());
}
public List<Jid> getCryptoTargets(Conversation conversation) { public List<Jid> getCryptoTargets(Conversation conversation) {
final List<Jid> jids; final List<Jid> jids;
if (conversation.getMode() == Conversation.MODE_SINGLE) { if (conversation.getMode() == Conversation.MODE_SINGLE) {

View File

@ -11,8 +11,10 @@ import org.json.JSONObject;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
@ -84,7 +86,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
private PgpDecryptionService pgpDecryptionService = null; private PgpDecryptionService pgpDecryptionService = null;
private XmppConnection xmppConnection = null; private XmppConnection xmppConnection = null;
private long mEndGracePeriod = 0L; private long mEndGracePeriod = 0L;
private List<Bookmark> bookmarks = new CopyOnWriteArrayList<>(); private final Map<Jid, Bookmark> bookmarks = new HashMap<>();
private Presence.Status presenceStatus = Presence.Status.ONLINE; private Presence.Status presenceStatus = Presence.Status.ONLINE;
private String presenceStatusMessage = null; private String presenceStatusMessage = null;
@ -469,36 +471,51 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
return this.roster; return this.roster;
} }
public List<Bookmark> getBookmarks() { public Collection<Bookmark> getBookmarks() {
return this.bookmarks; return this.bookmarks.values();
} }
public void setBookmarks(final CopyOnWriteArrayList<Bookmark> bookmarks) { public void setBookmarks(Map<Jid, Bookmark> bookmarks) {
this.bookmarks = bookmarks; synchronized (this.bookmarks) {
this.bookmarks.clear();
this.bookmarks.putAll(bookmarks);
}
}
public void putBookmark(Bookmark bookmark) {
synchronized (this.bookmarks) {
this.bookmarks.put(bookmark.getJid(), bookmark);
}
}
public void removeBookmark(Bookmark bookmark) {
synchronized (this.bookmarks) {
this.bookmarks.remove(bookmark.getJid());
}
}
public void removeBookmark(Jid jid) {
synchronized (this.bookmarks) {
this.bookmarks.remove(jid);
}
} }
public Set<Jid> getBookmarkedJids() { public Set<Jid> getBookmarkedJids() {
final Set<Jid> jids = new HashSet<>(); synchronized (this.bookmarks) {
for(final Bookmark bookmark : this.bookmarks) { return new HashSet<>(this.bookmarks.keySet());
final Jid jid = bookmark.getJid();
if (jid != null) {
jids.add(jid.asBareJid());
}
} }
return jids;
} }
public boolean hasBookmarkFor(final Jid conferenceJid) { public boolean hasBookmarkFor(final Jid jid) {
return getBookmark(conferenceJid) != null; synchronized (this.bookmarks) {
return this.bookmarks.containsKey(jid.asBareJid());
}
} }
Bookmark getBookmark(final Jid jid) { Bookmark getBookmark(final Jid jid) {
for (final Bookmark bookmark : this.bookmarks) { synchronized (this.bookmarks) {
if (bookmark.getJid() != null && jid.asBareJid().equals(bookmark.getJid().asBareJid())) { return this.bookmarks.get(jid.asBareJid());
return bookmark;
}
} }
return null;
} }
public boolean setAvatar(final String filename) { public boolean setAvatar(final String filename) {

View File

@ -6,12 +6,16 @@ import android.support.annotation.Nullable;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import eu.siacs.conversations.utils.StringUtils; import eu.siacs.conversations.utils.StringUtils;
import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.InvalidJid;
import rocks.xmpp.addr.Jid; import rocks.xmpp.addr.Jid;
@ -33,11 +37,69 @@ public class Bookmark extends Element implements ListItem {
this.account = account; this.account = account;
} }
public static Map<Jid, Bookmark> parseFromStorage(Element storage, Account account) {
if (storage == null) {
return Collections.emptyMap();
}
final HashMap<Jid, Bookmark> bookmarks = new HashMap<>();
for (final Element item : storage.getChildren()) {
if (item.getName().equals("conference")) {
final Bookmark bookmark = Bookmark.parse(item, account);
if (bookmark != null) {
final Bookmark old = bookmarks.put(bookmark.jid, bookmark);
if (old != null && old.getBookmarkName() != null && bookmark.getBookmarkName() == null) {
bookmark.setBookmarkName(old.getBookmarkName());
}
}
}
}
return bookmarks;
}
public static Map<Jid, Bookmark> parseFromPubsub(Element pubsub, Account account) {
if (pubsub == null) {
return Collections.emptyMap();
}
final Element items = pubsub.findChild("items");
if (items != null && Namespace.BOOKMARKS2.equals(items.getAttribute("node"))) {
final Map<Jid, Bookmark> bookmarks = new HashMap<>();
for(Element item : items.getChildren()) {
if (item.getName().equals("item")) {
final Bookmark bookmark = Bookmark.parseFromItem(item, account);
if (bookmark != null) {
bookmarks.put(bookmark.jid, bookmark);
}
}
}
return bookmarks;
}
return Collections.emptyMap();
}
public static Bookmark parse(Element element, Account account) { public static Bookmark parse(Element element, Account account) {
Bookmark bookmark = new Bookmark(account); Bookmark bookmark = new Bookmark(account);
bookmark.setAttributes(element.getAttributes()); bookmark.setAttributes(element.getAttributes());
bookmark.setChildren(element.getChildren()); bookmark.setChildren(element.getChildren());
bookmark.jid = InvalidJid.getNullForInvalid(bookmark.getAttributeAsJid("jid")); bookmark.jid = InvalidJid.getNullForInvalid(bookmark.getAttributeAsJid("jid"));
if (bookmark.jid == null) {
return null;
}
return bookmark;
}
public static Bookmark parseFromItem(Element item, Account account) {
final Element conference = item.findChild("conference", Namespace.BOOKMARKS2);
if (conference == null) {
return null;
}
final Bookmark bookmark = new Bookmark(account);
bookmark.jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("id"));
if (bookmark.jid == null) {
return null;
}
bookmark.setBookmarkName(conference.getAttribute("name"));
bookmark.setAutojoin(conference.getAttributeAsBoolean("autojoin"));
bookmark.setNick(conference.findChildContent("nick"));
return bookmark; return bookmark;
} }

View File

@ -307,8 +307,10 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
synchronized (this.messages) { synchronized (this.messages) {
for (int i = this.messages.size() - 1; i >= 0; --i) { for (int i = this.messages.size() - 1; i >= 0; --i) {
final Message message = messages.get(i); final Message message = messages.get(i);
if (counterpart.equals(message.getCounterpart()) final boolean counterpartMatch = mode == MODE_SINGLE ?
&& ((message.getStatus() == Message.STATUS_RECEIVED) == received) counterpart.asBareJid().equals(message.getCounterpart().asBareJid()) :
counterpart.equals(message.getCounterpart());
if (counterpartMatch && ((message.getStatus() == Message.STATUS_RECEIVED) == received)
&& (carbon == message.isCarbon() || received)) { && (carbon == message.isCarbon() || received)) {
final boolean idMatch = id.equals(message.getRemoteMsgId()) || message.remoteMsgIdMatchInEdit(id); final boolean idMatch = id.equals(message.getRemoteMsgId()) || message.remoteMsgIdMatchInEdit(id);
if (idMatch && !message.isFileOrImage() && !message.treatAsDownloadable()) { if (idMatch && !message.isFileOrImage() && !message.treatAsDownloadable()) {

View File

@ -417,7 +417,7 @@ public class MucOptions {
} }
} }
private String getProposedNick() { public String getProposedNick() {
final Bookmark bookmark = this.conversation.getBookmark(); final Bookmark bookmark = this.conversation.getBookmark();
final String bookmarkedNick = normalize(account.getJid(), bookmark == null ? null : bookmark.getNick()); final String bookmarkedNick = normalize(account.getJid(), bookmark == null ? null : bookmark.getNick());
if (bookmarkedNick != null) { if (bookmarkedNick != null) {

View File

@ -15,6 +15,7 @@ public interface Transferable {
int STATUS_DOWNLOADING = 0x204; int STATUS_DOWNLOADING = 0x204;
int STATUS_OFFER_CHECK_FILESIZE = 0x206; int STATUS_OFFER_CHECK_FILESIZE = 0x206;
int STATUS_UPLOADING = 0x207; int STATUS_UPLOADING = 0x207;
int STATUS_CANCELLED = 0x208;
boolean start(); boolean start();

View File

@ -19,6 +19,7 @@ import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.PhoneHelper; import eu.siacs.conversations.utils.PhoneHelper;
import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.XmppConnection;
import eu.siacs.conversations.xmpp.jingle.stanzas.Content; import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
public abstract class AbstractGenerator { public abstract class AbstractGenerator {
@ -38,7 +39,6 @@ public abstract class AbstractGenerator {
"http://jabber.org/protocol/disco#info", "http://jabber.org/protocol/disco#info",
"urn:xmpp:avatar:metadata+notify", "urn:xmpp:avatar:metadata+notify",
Namespace.NICK+"+notify", Namespace.NICK+"+notify",
Namespace.BOOKMARKS+"+notify",
"urn:xmpp:ping", "urn:xmpp:ping",
"jabber:iq:version", "jabber:iq:version",
"http://jabber.org/protocol/chatstates" "http://jabber.org/protocol/chatstates"
@ -109,7 +109,8 @@ public abstract class AbstractGenerator {
} }
public List<String> getFeatures(Account account) { public List<String> getFeatures(Account account) {
ArrayList<String> features = new ArrayList<>(Arrays.asList(FEATURES)); final XmppConnection connection = account.getXmppConnection();
final ArrayList<String> features = new ArrayList<>(Arrays.asList(FEATURES));
if (mXmppConnectionService.confirmMessages()) { if (mXmppConnectionService.confirmMessages()) {
features.addAll(Arrays.asList(MESSAGE_CONFIRMATION_FEATURES)); features.addAll(Arrays.asList(MESSAGE_CONFIRMATION_FEATURES));
} }
@ -125,6 +126,12 @@ public abstract class AbstractGenerator {
if (mXmppConnectionService.broadcastLastActivity()) { if (mXmppConnectionService.broadcastLastActivity()) {
features.add(Namespace.IDLE); features.add(Namespace.IDLE);
} }
if (connection != null && connection.getFeatures().bookmarks2()) {
features.add(Namespace.BOOKMARKS2 +"+notify");
} else {
features.add(Namespace.BOOKMARKS+"+notify");
}
Collections.sort(features); Collections.sort(features);
return features; return features;
} }

View File

@ -38,498 +38,531 @@ import rocks.xmpp.addr.Jid;
public class IqGenerator extends AbstractGenerator { public class IqGenerator extends AbstractGenerator {
public IqGenerator(final XmppConnectionService service) { public IqGenerator(final XmppConnectionService service) {
super(service); super(service);
} }
public IqPacket discoResponse(final Account account, final IqPacket request) { public IqPacket discoResponse(final Account account, final IqPacket request) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.RESULT); final IqPacket packet = new IqPacket(IqPacket.TYPE.RESULT);
packet.setId(request.getId()); packet.setId(request.getId());
packet.setTo(request.getFrom()); packet.setTo(request.getFrom());
final Element query = packet.addChild("query", "http://jabber.org/protocol/disco#info"); final Element query = packet.addChild("query", "http://jabber.org/protocol/disco#info");
query.setAttribute("node", request.query().getAttribute("node")); query.setAttribute("node", request.query().getAttribute("node"));
final Element identity = query.addChild("identity"); final Element identity = query.addChild("identity");
identity.setAttribute("category", "client"); identity.setAttribute("category", "client");
identity.setAttribute("type", getIdentityType()); identity.setAttribute("type", getIdentityType());
identity.setAttribute("name", getIdentityName()); identity.setAttribute("name", getIdentityName());
for (final String feature : getFeatures(account)) { for (final String feature : getFeatures(account)) {
query.addChild("feature").setAttribute("var", feature); query.addChild("feature").setAttribute("var", feature);
} }
return packet; return packet;
} }
public IqPacket versionResponse(final IqPacket request) { public IqPacket versionResponse(final IqPacket request) {
final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT); final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT);
Element query = packet.query("jabber:iq:version"); Element query = packet.query("jabber:iq:version");
query.addChild("name").setContent(mXmppConnectionService.getString(R.string.app_name)); query.addChild("name").setContent(mXmppConnectionService.getString(R.string.app_name));
query.addChild("version").setContent(getIdentityVersion()); query.addChild("version").setContent(getIdentityVersion());
if ("chromium".equals(android.os.Build.BRAND)) { if ("chromium".equals(android.os.Build.BRAND)) {
query.addChild("os").setContent("Chrome OS"); query.addChild("os").setContent("Chrome OS");
} else { } else {
query.addChild("os").setContent("Android"); query.addChild("os").setContent("Android");
} }
return packet; return packet;
} }
public IqPacket entityTimeResponse(IqPacket request) { public IqPacket entityTimeResponse(IqPacket request) {
final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT); final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT);
Element time = packet.addChild("time", "urn:xmpp:time"); Element time = packet.addChild("time", "urn:xmpp:time");
final long now = System.currentTimeMillis(); final long now = System.currentTimeMillis();
time.addChild("utc").setContent(getTimestamp(now)); time.addChild("utc").setContent(getTimestamp(now));
TimeZone ourTimezone = TimeZone.getDefault(); TimeZone ourTimezone = TimeZone.getDefault();
long offsetSeconds = ourTimezone.getOffset(now) / 1000; long offsetSeconds = ourTimezone.getOffset(now) / 1000;
long offsetMinutes = Math.abs((offsetSeconds % 3600) / 60); long offsetMinutes = Math.abs((offsetSeconds % 3600) / 60);
long offsetHours = offsetSeconds / 3600; long offsetHours = offsetSeconds / 3600;
String hours; String hours;
if (offsetHours < 0) { if (offsetHours < 0) {
hours = String.format(Locale.US, "%03d", offsetHours); hours = String.format(Locale.US, "%03d", offsetHours);
} else { } else {
hours = String.format(Locale.US, "%02d", offsetHours); hours = String.format(Locale.US, "%02d", offsetHours);
} }
String minutes = String.format(Locale.US, "%02d", offsetMinutes); String minutes = String.format(Locale.US, "%02d", offsetMinutes);
time.addChild("tzo").setContent(hours + ":" + minutes); time.addChild("tzo").setContent(hours + ":" + minutes);
return packet; return packet;
} }
public IqPacket purgeOfflineMessages() { public IqPacket purgeOfflineMessages() {
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
packet.addChild("offline", Namespace.FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL).addChild("purge"); packet.addChild("offline", Namespace.FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL).addChild("purge");
return packet; return packet;
} }
protected IqPacket publish(final String node, final Element item, final Bundle options) { protected IqPacket publish(final String node, final Element item, final Bundle options) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB); final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
final Element publish = pubsub.addChild("publish"); final Element publish = pubsub.addChild("publish");
publish.setAttribute("node", node); publish.setAttribute("node", node);
publish.addChild(item); publish.addChild(item);
if (options != null) { if (options != null) {
final Element publishOptions = pubsub.addChild("publish-options"); final Element publishOptions = pubsub.addChild("publish-options");
publishOptions.addChild(Data.create(Namespace.PUBSUB_PUBLISH_OPTIONS, options)); publishOptions.addChild(Data.create(Namespace.PUBSUB_PUBLISH_OPTIONS, options));
} }
return packet; return packet;
} }
protected IqPacket publish(final String node, final Element item) { protected IqPacket publish(final String node, final Element item) {
return publish(node, item, null); return publish(node, item, null);
} }
private IqPacket retrieve(String node, Element item) { private IqPacket retrieve(String node, Element item) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.GET); final IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB); final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
final Element items = pubsub.addChild("items"); final Element items = pubsub.addChild("items");
items.setAttribute("node", node); items.setAttribute("node", node);
if (item != null) { if (item != null) {
items.addChild(item); items.addChild(item);
} }
return packet; return packet;
} }
public IqPacket publishNick(String nick) { public IqPacket retrieveBookmarks() {
final Element item = new Element("item"); return retrieve(Namespace.BOOKMARKS2, null);
item.addChild("nick", Namespace.NICK).setContent(nick); }
return publish(Namespace.NICK, item);
}
public IqPacket deleteNode(String node) { public IqPacket publishNick(String nick) {
IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final Element item = new Element("item");
final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB_OWNER); item.addChild("nick", Namespace.NICK).setContent(nick);
pubsub.addChild("delete").setAttribute("node",node); return publish(Namespace.NICK, item);
return packet; }
}
public IqPacket publishAvatar(Avatar avatar, Bundle options) { public IqPacket deleteNode(String node) {
final Element item = new Element("item"); IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
item.setAttribute("id", avatar.sha1sum); final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB_OWNER);
final Element data = item.addChild("data", "urn:xmpp:avatar:data"); pubsub.addChild("delete").setAttribute("node", node);
data.setContent(avatar.image); return packet;
return publish("urn:xmpp:avatar:data", item, options); }
}
public IqPacket publishElement(final String namespace,final Element element, final Bundle options) { public IqPacket deleteItem(final String node, final String id) {
final Element item = new Element("item"); IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
item.setAttribute("id","current"); final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
item.addChild(element); final Element retract = pubsub.addChild("retract");
return publish(namespace, item, options); retract.setAttribute("node", node);
} retract.setAttribute("notify","true");
retract.addChild("item").setAttribute("id", id);
return packet;
}
public IqPacket publishAvatarMetadata(final Avatar avatar, final Bundle options) { public IqPacket publishAvatar(Avatar avatar, Bundle options) {
final Element item = new Element("item"); final Element item = new Element("item");
item.setAttribute("id", avatar.sha1sum); item.setAttribute("id", avatar.sha1sum);
final Element metadata = item final Element data = item.addChild("data", "urn:xmpp:avatar:data");
.addChild("metadata", "urn:xmpp:avatar:metadata"); data.setContent(avatar.image);
final Element info = metadata.addChild("info"); return publish("urn:xmpp:avatar:data", item, options);
info.setAttribute("bytes", avatar.size); }
info.setAttribute("id", avatar.sha1sum);
info.setAttribute("height", avatar.height);
info.setAttribute("width", avatar.height);
info.setAttribute("type", avatar.type);
return publish("urn:xmpp:avatar:metadata", item, options);
}
public IqPacket retrievePepAvatar(final Avatar avatar) { public IqPacket publishElement(final String namespace, final Element element, final Bundle options) {
final Element item = new Element("item"); return publishElement(namespace, element, "curent", options);
item.setAttribute("id", avatar.sha1sum); }
final IqPacket packet = retrieve("urn:xmpp:avatar:data", item);
packet.setTo(avatar.owner);
return packet;
}
public IqPacket retrieveVcardAvatar(final Avatar avatar) { public IqPacket publishElement(final String namespace, final Element element, String id, final Bundle options) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.GET); final Element item = new Element("item");
packet.setTo(avatar.owner); item.setAttribute("id", id);
packet.addChild("vCard", "vcard-temp"); item.addChild(element);
return packet; return publish(namespace, item, options);
} }
public IqPacket retrieveAvatarMetaData(final Jid to) { public IqPacket publishAvatarMetadata(final Avatar avatar, final Bundle options) {
final IqPacket packet = retrieve("urn:xmpp:avatar:metadata", null); final Element item = new Element("item");
if (to != null) { item.setAttribute("id", avatar.sha1sum);
packet.setTo(to); final Element metadata = item
} .addChild("metadata", "urn:xmpp:avatar:metadata");
return packet; final Element info = metadata.addChild("info");
} info.setAttribute("bytes", avatar.size);
info.setAttribute("id", avatar.sha1sum);
info.setAttribute("height", avatar.height);
info.setAttribute("width", avatar.height);
info.setAttribute("type", avatar.type);
return publish("urn:xmpp:avatar:metadata", item, options);
}
public IqPacket retrieveDeviceIds(final Jid to) { public IqPacket retrievePepAvatar(final Avatar avatar) {
final IqPacket packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null); final Element item = new Element("item");
if (to != null) { item.setAttribute("id", avatar.sha1sum);
packet.setTo(to); final IqPacket packet = retrieve("urn:xmpp:avatar:data", item);
} packet.setTo(avatar.owner);
return packet; return packet;
} }
public IqPacket retrieveBundlesForDevice(final Jid to, final int deviceid) { public IqPacket retrieveVcardAvatar(final Avatar avatar) {
final IqPacket packet = retrieve(AxolotlService.PEP_BUNDLES + ":" + deviceid, null); final IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
packet.setTo(to); packet.setTo(avatar.owner);
return packet; packet.addChild("vCard", "vcard-temp");
} return packet;
}
public IqPacket retrieveVerificationForDevice(final Jid to, final int deviceid) { public IqPacket retrieveAvatarMetaData(final Jid to) {
final IqPacket packet = retrieve(AxolotlService.PEP_VERIFICATION + ":" + deviceid, null); final IqPacket packet = retrieve("urn:xmpp:avatar:metadata", null);
packet.setTo(to); if (to != null) {
return packet; packet.setTo(to);
} }
return packet;
}
public IqPacket publishDeviceIds(final Set<Integer> ids, final Bundle publishOptions) { public IqPacket retrieveDeviceIds(final Jid to) {
final Element item = new Element("item"); final IqPacket packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null);
item.setAttribute("id", "current"); if (to != null) {
final Element list = item.addChild("list", AxolotlService.PEP_PREFIX); packet.setTo(to);
for (Integer id : ids) { }
final Element device = new Element("device"); return packet;
device.setAttribute("id", id); }
list.addChild(device);
}
return publish(AxolotlService.PEP_DEVICE_LIST, item, publishOptions);
}
public IqPacket publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey, public IqPacket retrieveBundlesForDevice(final Jid to, final int deviceid) {
final Set<PreKeyRecord> preKeyRecords, final int deviceId, Bundle publishOptions) { final IqPacket packet = retrieve(AxolotlService.PEP_BUNDLES + ":" + deviceid, null);
final Element item = new Element("item"); packet.setTo(to);
item.setAttribute("id", "current"); return packet;
final Element bundle = item.addChild("bundle", AxolotlService.PEP_PREFIX); }
final Element signedPreKeyPublic = bundle.addChild("signedPreKeyPublic");
signedPreKeyPublic.setAttribute("signedPreKeyId", signedPreKeyRecord.getId());
ECPublicKey publicKey = signedPreKeyRecord.getKeyPair().getPublicKey();
signedPreKeyPublic.setContent(Base64.encodeToString(publicKey.serialize(), Base64.DEFAULT));
final Element signedPreKeySignature = bundle.addChild("signedPreKeySignature");
signedPreKeySignature.setContent(Base64.encodeToString(signedPreKeyRecord.getSignature(), Base64.DEFAULT));
final Element identityKeyElement = bundle.addChild("identityKey");
identityKeyElement.setContent(Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT));
final Element prekeys = bundle.addChild("prekeys", AxolotlService.PEP_PREFIX); public IqPacket retrieveVerificationForDevice(final Jid to, final int deviceid) {
for (PreKeyRecord preKeyRecord : preKeyRecords) { final IqPacket packet = retrieve(AxolotlService.PEP_VERIFICATION + ":" + deviceid, null);
final Element prekey = prekeys.addChild("preKeyPublic"); packet.setTo(to);
prekey.setAttribute("preKeyId", preKeyRecord.getId()); return packet;
prekey.setContent(Base64.encodeToString(preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.DEFAULT)); }
}
return publish(AxolotlService.PEP_BUNDLES + ":" + deviceId, item, publishOptions); public IqPacket publishDeviceIds(final Set<Integer> ids, final Bundle publishOptions) {
} final Element item = new Element("item");
item.setAttribute("id", "current");
final Element list = item.addChild("list", AxolotlService.PEP_PREFIX);
for (Integer id : ids) {
final Element device = new Element("device");
device.setAttribute("id", id);
list.addChild(device);
}
return publish(AxolotlService.PEP_DEVICE_LIST, item, publishOptions);
}
public IqPacket publishVerification(byte[] signature, X509Certificate[] certificates, final int deviceId) { public Element publishBookmarkItem(final Bookmark bookmark) {
final Element item = new Element("item"); final String name = bookmark.getBookmarkName();
item.setAttribute("id", "current"); final String nick = bookmark.getNick();
final Element verification = item.addChild("verification", AxolotlService.PEP_PREFIX); final boolean autojoin = bookmark.autojoin();
final Element chain = verification.addChild("chain"); final Element conference = new Element("conference", Namespace.BOOKMARKS2);
for (int i = 0; i < certificates.length; ++i) { if (name != null) {
try { conference.setAttribute("name", name);
Element certificate = chain.addChild("certificate"); }
certificate.setContent(Base64.encodeToString(certificates[i].getEncoded(), Base64.DEFAULT)); if (nick != null) {
certificate.setAttribute("index", i); conference.addChild("nick").setContent(nick);
} catch (CertificateEncodingException e) { }
Log.d(Config.LOGTAG, "could not encode certificate"); conference.setAttribute("autojoin",String.valueOf(autojoin));
} return conference;
} }
verification.addChild("signature").setContent(Base64.encodeToString(signature, Base64.DEFAULT));
return publish(AxolotlService.PEP_VERIFICATION + ":" + deviceId, item);
}
public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) { public IqPacket publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey,
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final Set<PreKeyRecord> preKeyRecords, final int deviceId, Bundle publishOptions) {
final Element query = packet.query(mam.version.namespace); final Element item = new Element("item");
query.setAttribute("queryid", mam.getQueryId()); item.setAttribute("id", "current");
final Data data = new Data(); final Element bundle = item.addChild("bundle", AxolotlService.PEP_PREFIX);
data.setFormType(mam.version.namespace); final Element signedPreKeyPublic = bundle.addChild("signedPreKeyPublic");
if (mam.muc()) { signedPreKeyPublic.setAttribute("signedPreKeyId", signedPreKeyRecord.getId());
packet.setTo(mam.getWith()); ECPublicKey publicKey = signedPreKeyRecord.getKeyPair().getPublicKey();
} else if (mam.getWith() != null) { signedPreKeyPublic.setContent(Base64.encodeToString(publicKey.serialize(), Base64.DEFAULT));
data.put("with", mam.getWith().toString()); final Element signedPreKeySignature = bundle.addChild("signedPreKeySignature");
} signedPreKeySignature.setContent(Base64.encodeToString(signedPreKeyRecord.getSignature(), Base64.DEFAULT));
final long start = mam.getStart(); final Element identityKeyElement = bundle.addChild("identityKey");
final long end = mam.getEnd(); identityKeyElement.setContent(Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT));
if (start != 0) {
data.put("start", getTimestamp(start));
}
if (end != 0) {
data.put("end", getTimestamp(end));
}
data.submit();
query.addChild(data);
Element set = query.addChild("set", "http://jabber.org/protocol/rsm");
if (mam.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
set.addChild("before").setContent(mam.getReference());
} else if (mam.getReference() != null) {
set.addChild("after").setContent(mam.getReference());
}
set.addChild("max").setContent(String.valueOf(Config.PAGE_SIZE));
return packet;
}
public IqPacket generateGetBlockList() { final Element prekeys = bundle.addChild("prekeys", AxolotlService.PEP_PREFIX);
final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); for (PreKeyRecord preKeyRecord : preKeyRecords) {
iq.addChild("blocklist", Namespace.BLOCKING); final Element prekey = prekeys.addChild("preKeyPublic");
prekey.setAttribute("preKeyId", preKeyRecord.getId());
prekey.setContent(Base64.encodeToString(preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.DEFAULT));
}
return iq; return publish(AxolotlService.PEP_BUNDLES + ":" + deviceId, item, publishOptions);
} }
public IqPacket generateSetBlockRequest(final Jid jid, boolean reportSpam) { public IqPacket publishVerification(byte[] signature, X509Certificate[] certificates, final int deviceId) {
final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); final Element item = new Element("item");
final Element block = iq.addChild("block", Namespace.BLOCKING); item.setAttribute("id", "current");
final Element item = block.addChild("item").setAttribute("jid", jid.toEscapedString()); final Element verification = item.addChild("verification", AxolotlService.PEP_PREFIX);
if (reportSpam) { final Element chain = verification.addChild("chain");
item.addChild("report", "urn:xmpp:reporting:0").addChild("spam"); for (int i = 0; i < certificates.length; ++i) {
} try {
Log.d(Config.LOGTAG, iq.toString()); Element certificate = chain.addChild("certificate");
return iq; certificate.setContent(Base64.encodeToString(certificates[i].getEncoded(), Base64.DEFAULT));
} certificate.setAttribute("index", i);
} catch (CertificateEncodingException e) {
Log.d(Config.LOGTAG, "could not encode certificate");
}
}
verification.addChild("signature").setContent(Base64.encodeToString(signature, Base64.DEFAULT));
return publish(AxolotlService.PEP_VERIFICATION + ":" + deviceId, item);
}
public IqPacket generateSetUnblockRequest(final Jid jid) { public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) {
final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
final Element block = iq.addChild("unblock", Namespace.BLOCKING); final Element query = packet.query(mam.version.namespace);
block.addChild("item").setAttribute("jid", jid.toEscapedString()); query.setAttribute("queryid", mam.getQueryId());
return iq; final Data data = new Data();
} data.setFormType(mam.version.namespace);
if (mam.muc()) {
packet.setTo(mam.getWith());
} else if (mam.getWith() != null) {
data.put("with", mam.getWith().toString());
}
final long start = mam.getStart();
final long end = mam.getEnd();
if (start != 0) {
data.put("start", getTimestamp(start));
}
if (end != 0) {
data.put("end", getTimestamp(end));
}
data.submit();
query.addChild(data);
Element set = query.addChild("set", "http://jabber.org/protocol/rsm");
if (mam.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
set.addChild("before").setContent(mam.getReference());
} else if (mam.getReference() != null) {
set.addChild("after").setContent(mam.getReference());
}
set.addChild("max").setContent(String.valueOf(Config.PAGE_SIZE));
return packet;
}
public IqPacket generateSetPassword(final Account account, final String newPassword) { public IqPacket generateGetBlockList() {
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
packet.setTo(Jid.of(account.getServer())); iq.addChild("blocklist", Namespace.BLOCKING);
final Element query = packet.addChild("query", Namespace.REGISTER);
final Jid jid = account.getJid();
query.addChild("username").setContent(jid.getLocal());
query.addChild("password").setContent(newPassword);
return packet;
}
public IqPacket changeAffiliation(Conversation conference, Jid jid, String affiliation) { return iq;
List<Jid> jids = new ArrayList<>(); }
jids.add(jid);
return changeAffiliation(conference, jids, affiliation);
}
public IqPacket changeAffiliation(Conversation conference, List<Jid> jids, String affiliation) { public IqPacket generateSetBlockRequest(final Jid jid, boolean reportSpam) {
IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
packet.setTo(conference.getJid().asBareJid()); final Element block = iq.addChild("block", Namespace.BLOCKING);
packet.setFrom(conference.getAccount().getJid()); final Element item = block.addChild("item").setAttribute("jid", jid.toEscapedString());
Element query = packet.query("http://jabber.org/protocol/muc#admin"); if (reportSpam) {
for (Jid jid : jids) { item.addChild("report", "urn:xmpp:reporting:0").addChild("spam");
Element item = query.addChild("item"); }
item.setAttribute("jid", jid.toEscapedString()); Log.d(Config.LOGTAG, iq.toString());
item.setAttribute("affiliation", affiliation); return iq;
} }
return packet;
}
public IqPacket changeRole(Conversation conference, String nick, String role) { public IqPacket generateSetUnblockRequest(final Jid jid) {
IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
packet.setTo(conference.getJid().asBareJid()); final Element block = iq.addChild("unblock", Namespace.BLOCKING);
packet.setFrom(conference.getAccount().getJid()); block.addChild("item").setAttribute("jid", jid.toEscapedString());
Element item = packet.query("http://jabber.org/protocol/muc#admin").addChild("item"); return iq;
item.setAttribute("nick", nick); }
item.setAttribute("role", role);
return packet;
}
public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file, String mime) { public IqPacket generateSetPassword(final Account account, final String newPassword) {
IqPacket packet = new IqPacket(IqPacket.TYPE.GET); final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
packet.setTo(host); packet.setTo(Jid.of(account.getServer()));
Element request = packet.addChild("request", Namespace.HTTP_UPLOAD); final Element query = packet.addChild("query", Namespace.REGISTER);
request.setAttribute("filename", convertFilename(file.getName())); final Jid jid = account.getJid();
request.setAttribute("size", file.getExpectedSize()); query.addChild("username").setContent(jid.getLocal());
request.setAttribute("content-type", mime); query.addChild("password").setContent(newPassword);
return packet; return packet;
} }
public IqPacket requestHttpUploadLegacySlot(Jid host, DownloadableFile file, String mime) { public IqPacket changeAffiliation(Conversation conference, Jid jid, String affiliation) {
IqPacket packet = new IqPacket(IqPacket.TYPE.GET); List<Jid> jids = new ArrayList<>();
packet.setTo(host); jids.add(jid);
Element request = packet.addChild("request", Namespace.HTTP_UPLOAD_LEGACY); return changeAffiliation(conference, jids, affiliation);
request.addChild("filename").setContent(convertFilename(file.getName())); }
request.addChild("size").setContent(String.valueOf(file.getExpectedSize()));
request.addChild("content-type").setContent(mime);
return packet;
}
public IqPacket requestP1S3Slot(Jid host, String md5) { public IqPacket changeAffiliation(Conversation conference, List<Jid> jids, String affiliation) {
IqPacket packet = new IqPacket(IqPacket.TYPE.SET); IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
packet.setTo(host); packet.setTo(conference.getJid().asBareJid());
packet.query(Namespace.P1_S3_FILE_TRANSFER).setAttribute("md5", md5); packet.setFrom(conference.getAccount().getJid());
return packet; Element query = packet.query("http://jabber.org/protocol/muc#admin");
} for (Jid jid : jids) {
Element item = query.addChild("item");
item.setAttribute("jid", jid.toEscapedString());
item.setAttribute("affiliation", affiliation);
}
return packet;
}
public IqPacket requestP1S3Url(Jid host, String fileId) { public IqPacket changeRole(Conversation conference, String nick, String role) {
IqPacket packet = new IqPacket(IqPacket.TYPE.GET); IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
packet.setTo(host); packet.setTo(conference.getJid().asBareJid());
packet.query(Namespace.P1_S3_FILE_TRANSFER).setAttribute("fileid", fileId); packet.setFrom(conference.getAccount().getJid());
return packet; Element item = packet.query("http://jabber.org/protocol/muc#admin").addChild("item");
} item.setAttribute("nick", nick);
item.setAttribute("role", role);
return packet;
}
private static String convertFilename(String name) { public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file, String mime) {
int pos = name.indexOf('.'); IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
if (pos != -1) { packet.setTo(host);
try { Element request = packet.addChild("request", Namespace.HTTP_UPLOAD);
UUID uuid = UUID.fromString(name.substring(0, pos)); request.setAttribute("filename", convertFilename(file.getName()));
ByteBuffer bb = ByteBuffer.wrap(new byte[16]); request.setAttribute("size", file.getExpectedSize());
bb.putLong(uuid.getMostSignificantBits()); request.setAttribute("content-type", mime);
bb.putLong(uuid.getLeastSignificantBits()); return packet;
return Base64.encodeToString(bb.array(), Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP) + name.substring(pos, name.length()); }
} catch (Exception e) {
return name;
}
} else {
return name;
}
}
public IqPacket generateCreateAccountWithCaptcha(Account account, String id, Data data) { public IqPacket requestHttpUploadLegacySlot(Jid host, DownloadableFile file, String mime) {
final IqPacket register = new IqPacket(IqPacket.TYPE.SET); IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
register.setFrom(account.getJid().asBareJid()); packet.setTo(host);
register.setTo(Jid.of(account.getServer())); Element request = packet.addChild("request", Namespace.HTTP_UPLOAD_LEGACY);
register.setId(id); request.addChild("filename").setContent(convertFilename(file.getName()));
Element query = register.query(Namespace.REGISTER); request.addChild("size").setContent(String.valueOf(file.getExpectedSize()));
if (data != null) { request.addChild("content-type").setContent(mime);
query.addChild(data); return packet;
} }
return register;
}
public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId) { public IqPacket requestP1S3Slot(Jid host, String md5) {
return pushTokenToAppServer(appServer, token, deviceId, null); IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
} packet.setTo(host);
packet.query(Namespace.P1_S3_FILE_TRANSFER).setAttribute("md5", md5);
return packet;
}
public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId, Jid muc) { public IqPacket requestP1S3Url(Jid host, String fileId) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
packet.setTo(appServer); packet.setTo(host);
final Element command = packet.addChild("command", Namespace.COMMANDS); packet.query(Namespace.P1_S3_FILE_TRANSFER).setAttribute("fileid", fileId);
command.setAttribute("node", "register-push-fcm"); return packet;
command.setAttribute("action", "execute"); }
final Data data = new Data();
data.put("token", token);
data.put("android-id", deviceId);
if (muc != null) {
data.put("muc", muc.toEscapedString());
}
data.submit();
command.addChild(data);
return packet;
}
public IqPacket unregisterChannelOnAppServer(Jid appServer, String deviceId, String channel) { private static String convertFilename(String name) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); int pos = name.indexOf('.');
packet.setTo(appServer); if (pos != -1) {
final Element command = packet.addChild("command", Namespace.COMMANDS); try {
command.setAttribute("node", "unregister-push-fcm"); UUID uuid = UUID.fromString(name.substring(0, pos));
command.setAttribute("action", "execute"); ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
final Data data = new Data(); bb.putLong(uuid.getMostSignificantBits());
data.put("channel", channel); bb.putLong(uuid.getLeastSignificantBits());
data.put("android-id", deviceId); return Base64.encodeToString(bb.array(), Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP) + name.substring(pos, name.length());
data.submit(); } catch (Exception e) {
command.addChild(data); return name;
return packet; }
} } else {
return name;
}
}
public IqPacket enablePush(final Jid jid, final String node, final String secret) { public IqPacket generateCreateAccountWithCaptcha(Account account, String id, Data data) {
IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final IqPacket register = new IqPacket(IqPacket.TYPE.SET);
Element enable = packet.addChild("enable", Namespace.PUSH); register.setFrom(account.getJid().asBareJid());
enable.setAttribute("jid", jid.toString()); register.setTo(Jid.of(account.getServer()));
enable.setAttribute("node", node); register.setId(id);
if (secret != null) { Element query = register.query(Namespace.REGISTER);
Data data = new Data(); if (data != null) {
data.setFormType(Namespace.PUBSUB_PUBLISH_OPTIONS); query.addChild(data);
data.put("secret", secret); }
data.submit(); return register;
enable.addChild(data); }
}
return packet;
}
public IqPacket disablePush(final Jid jid, final String node) { public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId) {
IqPacket packet = new IqPacket(IqPacket.TYPE.SET); return pushTokenToAppServer(appServer, token, deviceId, null);
Element disable = packet.addChild("disable", Namespace.PUSH); }
disable.setAttribute("jid", jid.toEscapedString());
disable.setAttribute("node", node);
return packet;
}
public IqPacket queryAffiliation(Conversation conversation, String affiliation) { public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId, Jid muc) {
IqPacket packet = new IqPacket(IqPacket.TYPE.GET); final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
packet.setTo(conversation.getJid().asBareJid()); packet.setTo(appServer);
packet.query("http://jabber.org/protocol/muc#admin").addChild("item").setAttribute("affiliation", affiliation); final Element command = packet.addChild("command", Namespace.COMMANDS);
return packet; command.setAttribute("node", "register-push-fcm");
} command.setAttribute("action", "execute");
final Data data = new Data();
data.put("token", token);
data.put("android-id", deviceId);
if (muc != null) {
data.put("muc", muc.toEscapedString());
}
data.submit();
command.addChild(data);
return packet;
}
public static Bundle defaultGroupChatConfiguration() { public IqPacket unregisterChannelOnAppServer(Jid appServer, String deviceId, String channel) {
Bundle options = new Bundle(); final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
options.putString("muc#roomconfig_persistentroom", "1"); packet.setTo(appServer);
options.putString("muc#roomconfig_membersonly", "1"); final Element command = packet.addChild("command", Namespace.COMMANDS);
options.putString("muc#roomconfig_publicroom", "0"); command.setAttribute("node", "unregister-push-fcm");
options.putString("muc#roomconfig_whois", "anyone"); command.setAttribute("action", "execute");
options.putString("muc#roomconfig_changesubject", "0"); final Data data = new Data();
options.putString("muc#roomconfig_allowinvites", "0"); data.put("channel", channel);
options.putString("muc#roomconfig_enablearchiving", "1"); //prosody data.put("android-id", deviceId);
options.putString("mam", "1"); //ejabberd community data.submit();
options.putString("muc#roomconfig_mam","1"); //ejabberd saas command.addChild(data);
return options; return packet;
} }
public static Bundle defaultChannelConfiguration() { public IqPacket enablePush(final Jid jid, final String node, final String secret) {
Bundle options = new Bundle(); IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
options.putString("muc#roomconfig_persistentroom", "1"); Element enable = packet.addChild("enable", Namespace.PUSH);
options.putString("muc#roomconfig_membersonly", "0"); enable.setAttribute("jid", jid.toString());
options.putString("muc#roomconfig_publicroom", "1"); enable.setAttribute("node", node);
options.putString("muc#roomconfig_whois", "moderators"); if (secret != null) {
options.putString("muc#roomconfig_changesubject", "0"); Data data = new Data();
options.putString("muc#roomconfig_enablearchiving", "1"); //prosody data.setFormType(Namespace.PUBSUB_PUBLISH_OPTIONS);
options.putString("mam", "1"); //ejabberd community data.put("secret", secret);
options.putString("muc#roomconfig_mam","1"); //ejabberd saas data.submit();
return options; enable.addChild(data);
} }
return packet;
}
public IqPacket requestPubsubConfiguration(Jid jid, String node) { public IqPacket disablePush(final Jid jid, final String node) {
return pubsubConfiguration(jid, node, null); IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
} Element disable = packet.addChild("disable", Namespace.PUSH);
disable.setAttribute("jid", jid.toEscapedString());
disable.setAttribute("node", node);
return packet;
}
public IqPacket publishPubsubConfiguration(Jid jid, String node, Data data) { public IqPacket queryAffiliation(Conversation conversation, String affiliation) {
return pubsubConfiguration(jid, node, data); IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
} packet.setTo(conversation.getJid().asBareJid());
packet.query("http://jabber.org/protocol/muc#admin").addChild("item").setAttribute("affiliation", affiliation);
return packet;
}
private IqPacket pubsubConfiguration(Jid jid, String node, Data data) { public static Bundle defaultGroupChatConfiguration() {
IqPacket packet = new IqPacket(data == null ? IqPacket.TYPE.GET : IqPacket.TYPE.SET); Bundle options = new Bundle();
packet.setTo(jid); options.putString("muc#roomconfig_persistentroom", "1");
Element pubsub = packet.addChild("pubsub", "http://jabber.org/protocol/pubsub#owner"); options.putString("muc#roomconfig_membersonly", "1");
Element configure = pubsub.addChild("configure").setAttribute("node", node); options.putString("muc#roomconfig_publicroom", "0");
if (data != null) { options.putString("muc#roomconfig_whois", "anyone");
configure.addChild(data); options.putString("muc#roomconfig_changesubject", "0");
} options.putString("muc#roomconfig_allowinvites", "0");
return packet; options.putString("muc#roomconfig_enablearchiving", "1"); //prosody
} options.putString("mam", "1"); //ejabberd community
options.putString("muc#roomconfig_mam", "1"); //ejabberd saas
return options;
}
public static Bundle defaultChannelConfiguration() {
Bundle options = new Bundle();
options.putString("muc#roomconfig_persistentroom", "1");
options.putString("muc#roomconfig_membersonly", "0");
options.putString("muc#roomconfig_publicroom", "1");
options.putString("muc#roomconfig_whois", "moderators");
options.putString("muc#roomconfig_changesubject", "0");
options.putString("muc#roomconfig_enablearchiving", "1"); //prosody
options.putString("mam", "1"); //ejabberd community
options.putString("muc#roomconfig_mam", "1"); //ejabberd saas
return options;
}
public IqPacket requestPubsubConfiguration(Jid jid, String node) {
return pubsubConfiguration(jid, node, null);
}
public IqPacket publishPubsubConfiguration(Jid jid, String node, Data data) {
return pubsubConfiguration(jid, node, data);
}
private IqPacket pubsubConfiguration(Jid jid, String node, Data data) {
IqPacket packet = new IqPacket(data == null ? IqPacket.TYPE.GET : IqPacket.TYPE.SET);
packet.setTo(jid);
Element pubsub = packet.addChild("pubsub", "http://jabber.org/protocol/pubsub#owner");
Element configure = pubsub.addChild("configure").setAttribute("node", node);
if (data != null) {
configure.addChild(data);
}
return packet;
}
} }

View File

@ -10,6 +10,7 @@ import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -20,6 +21,7 @@ import eu.siacs.conversations.crypto.axolotl.BrokenSessionException;
import eu.siacs.conversations.crypto.axolotl.NotEncryptedForThisDeviceException; import eu.siacs.conversations.crypto.axolotl.NotEncryptedForThisDeviceException;
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage; import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Bookmark;
import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Conversational; import eu.siacs.conversations.entities.Conversational;
@ -110,7 +112,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
return false; return false;
} }
private Message parseAxolotlChat(Element axolotlMessage, Jid from, Conversation conversation, int status, boolean checkedForDuplicates, boolean postpone) { private Message parseAxolotlChat(Element axolotlMessage, Jid from, Conversation conversation, int status, final boolean checkedForDuplicates, boolean postpone) {
final AxolotlService service = conversation.getAccount().getAxolotlService(); final AxolotlService service = conversation.getAccount().getAxolotlService();
final XmppAxolotlMessage xmppAxolotlMessage; final XmppAxolotlMessage xmppAxolotlMessage;
try { try {
@ -134,7 +136,6 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
} }
} else { } else {
Log.d(Config.LOGTAG,"ignoring broken session exception because checkForDuplicates failed"); Log.d(Config.LOGTAG,"ignoring broken session exception because checkForDuplicates failed");
//TODO should be still emit a failed message?
return null; return null;
} }
} catch (NotEncryptedForThisDeviceException e) { } catch (NotEncryptedForThisDeviceException e) {
@ -224,23 +225,55 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
if (account.getXmppConnection().getFeatures().bookmarksConversion()) { if (account.getXmppConnection().getFeatures().bookmarksConversion()) {
final Element i = items.findChild("item"); final Element i = items.findChild("item");
final Element storage = i == null ? null : i.findChild("storage", Namespace.BOOKMARKS); final Element storage = i == null ? null : i.findChild("storage", Namespace.BOOKMARKS);
mXmppConnectionService.processBookmarks(account, storage, true); Map<Jid, Bookmark> bookmarks = Bookmark.parseFromStorage(storage, account);
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": processing bookmark PEP event"); mXmppConnectionService.processBookmarksInitial(account, bookmarks, true);
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": processing bookmark PEP event");
} else { } else {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": ignoring bookmark PEP event because bookmark conversion was not detected"); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring bookmark PEP event because bookmark conversion was not detected");
} }
} else if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
final Element item = items.findChild("item");
final Element retract = items.findChild("retract");
if (item != null) {
final Bookmark bookmark = Bookmark.parseFromItem(item, account);
if (bookmark != null) {
account.putBookmark(bookmark);
mXmppConnectionService.processModifiedBookmark(bookmark);
mXmppConnectionService.updateConversationUi();
}
}
if (retract != null) {
final Jid id = InvalidJid.getNullForInvalid(retract.getAttributeAsJid("id"));
if (id != null) {
account.removeBookmark(id);
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": deleted bookmark for "+id);
mXmppConnectionService.processDeletedBookmark(account, id);
mXmppConnectionService.updateConversationUi();
}
}
} else {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+" received pubsub notification for node="+node);
} }
} }
private void parseDeleteEvent(final Element event, final Jid from, final Account account) { private void parseDeleteEvent(final Element event, final Jid from, final Account account) {
final Element delete = event.findChild("delete"); final Element delete = event.findChild("delete");
if (delete == null) { final String node = delete == null ? null : delete.getAttribute("node");
return;
}
String node = delete.getAttribute("node");
if (Namespace.NICK.equals(node)) { if (Namespace.NICK.equals(node)) {
Log.d(Config.LOGTAG, "parsing nick delete event from " + from); Log.d(Config.LOGTAG, "parsing nick delete event from " + from);
setNick(account, from, null); setNick(account, from, null);
} else if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
account.setBookmarks(Collections.emptyMap());
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": deleted bookmarks node");
}
}
private void parsePurgeEvent(final Element event, final Jid from, final Account account) {
final Element purge = event.findChild("purge");
final String node = purge == null ? null : purge.getAttribute("node");
if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
account.setBookmarks(Collections.emptyMap());
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": purged bookmarks");
} }
} }
@ -463,8 +496,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
origin = from; origin = from;
} }
//TODO either or is probably fine? final boolean liveMessage = query == null && !isTypeGroupChat && mucUserElement == null;
final boolean checkedForDuplicates = serverMsgId != null && remoteMsgId != null && !conversation.possibleDuplicate(serverMsgId, remoteMsgId); final boolean checkedForDuplicates = liveMessage || (serverMsgId != null && remoteMsgId != null && !conversation.possibleDuplicate(serverMsgId, remoteMsgId));
if (origin != null) { if (origin != null) {
message = parseAxolotlChat(axolotlEncrypted, origin, conversation, status, checkedForDuplicates,query != null); message = parseAxolotlChat(axolotlEncrypted, origin, conversation, status, checkedForDuplicates,query != null);
@ -835,6 +868,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
parseEvent(event, original.getFrom(), account); parseEvent(event, original.getFrom(), account);
} else if (event.hasChild("delete")) { } else if (event.hasChild("delete")) {
parseDeleteEvent(event, original.getFrom(), account); parseDeleteEvent(event, original.getFrom(), account);
} else if (event.hasChild("purge")) {
parsePurgeEvent(event, original.getFrom(), account);
} }
} }

View File

@ -56,6 +56,7 @@ import eu.siacs.conversations.Config;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.services.AttachFileToConversationRunnable;
import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.ui.RecordingActivity; import eu.siacs.conversations.ui.RecordingActivity;
import eu.siacs.conversations.ui.util.Attachment; import eu.siacs.conversations.ui.util.Attachment;
@ -111,6 +112,7 @@ public class FileBackend {
} }
public static boolean allFilesUnderSize(Context context, List<Attachment> attachments, long max) { public static boolean allFilesUnderSize(Context context, List<Attachment> attachments, long max) {
final boolean compressVideo = !AttachFileToConversationRunnable.getVideoCompression(context).equals("uncompressed");
if (max <= 0) { if (max <= 0) {
Log.d(Config.LOGTAG, "server did not report max file size for http upload"); Log.d(Config.LOGTAG, "server did not report max file size for http upload");
return true; //exception to be compatible with HTTP Upload < v0.2 return true; //exception to be compatible with HTTP Upload < v0.2
@ -120,7 +122,7 @@ public class FileBackend {
continue; continue;
} }
String mime = attachment.getMime(); String mime = attachment.getMime();
if (mime != null && mime.startsWith("video/")) { if (mime != null && mime.startsWith("video/") && compressVideo) {
try { try {
Dimensions dimensions = FileBackend.getVideoDimensions(context, attachment.getUri()); Dimensions dimensions = FileBackend.getVideoDimensions(context, attachment.getUri());
if (dimensions.getMin() > 720) { if (dimensions.getMin() > 720) {
@ -191,6 +193,14 @@ public class FileBackend {
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) + "/Camera/"; return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) + "/Camera/";
} }
public static Uri getUriForUri(Context context, Uri uri) {
if ("file".equals(uri.getScheme())) {
return getUriForFile(context, new File(uri.getPath()));
} else {
return uri;
}
}
public static Uri getUriForFile(Context context, File file) { public static Uri getUriForFile(Context context, File file) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N || Config.ONLY_INTERNAL_STORAGE) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N || Config.ONLY_INTERNAL_STORAGE) {
try { try {

View File

@ -1,5 +1,6 @@
package eu.siacs.conversations.services; package eu.siacs.conversations.services;
import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
@ -177,7 +178,11 @@ public class AttachFileToConversationRunnable implements Runnable, MediaTranscod
} }
private String getVideoCompression() { private String getVideoCompression() {
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mXmppConnectionService); return getVideoCompression(mXmppConnectionService);
return preferences.getString("video_compression", mXmppConnectionService.getResources().getString(R.string.video_compression)); }
public static String getVideoCompression(final Context context) {
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
return preferences.getString("video_compression", context.getResources().getString(R.string.video_compression));
} }
} }

View File

@ -21,6 +21,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.ActivityChannelDiscoveryBinding; import eu.siacs.conversations.databinding.ActivityChannelDiscoveryBinding;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
@ -217,20 +218,21 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
return false; return false;
} }
public void joinChannelSearchResult(String accountJid, MuclumbusService.Room result) { public void joinChannelSearchResult(String selectedAccount, MuclumbusService.Room result) {
final boolean syncAutojoin = getBooleanPreference("autojoin", R.bool.autojoin); final Jid jid = Config.DOMAIN_LOCK == null ? Jid.of(selectedAccount) : Jid.of(selectedAccount, Config.DOMAIN_LOCK, null);
Account account = xmppConnectionService.findAccountByJid(Jid.of(accountJid)); final boolean syncAutoJoin = getBooleanPreference("autojoin", R.bool.autojoin);
final Account account = xmppConnectionService.findAccountByJid(jid);
final Conversation conversation = xmppConnectionService.findOrCreateConversation(account, result.getRoom(), true, true, true); final Conversation conversation = xmppConnectionService.findOrCreateConversation(account, result.getRoom(), true, true, true);
if (conversation.getBookmark() != null) { Bookmark bookmark = conversation.getBookmark();
if (!conversation.getBookmark().autojoin() && syncAutojoin) { if (bookmark != null) {
conversation.getBookmark().setAutojoin(true); if (!bookmark.autojoin() && syncAutoJoin) {
xmppConnectionService.pushBookmarks(account); bookmark.setAutojoin(true);
xmppConnectionService.createBookmark(account, bookmark);
} }
} else { } else {
final Bookmark bookmark = new Bookmark(account, conversation.getJid().asBareJid()); bookmark = new Bookmark(account, conversation.getJid().asBareJid());
bookmark.setAutojoin(syncAutojoin); bookmark.setAutojoin(syncAutoJoin);
account.getBookmarks().add(bookmark); xmppConnectionService.createBookmark(account, bookmark);
xmppConnectionService.pushBookmarks(account);
} }
switchToConversation(conversation); switchToConversation(conversation);
} }

View File

@ -386,11 +386,10 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
} }
protected void deleteBookmark() { protected void deleteBookmark() {
Account account = mConversation.getAccount(); final Account account = mConversation.getAccount();
Bookmark bookmark = mConversation.getBookmark(); final Bookmark bookmark = mConversation.getBookmark();
account.getBookmarks().remove(bookmark);
bookmark.setConversation(null); bookmark.setConversation(null);
xmppConnectionService.pushBookmarks(account); xmppConnectionService.deleteBookmark(account, bookmark);
updateView(); updateView();
} }

View File

@ -1044,6 +1044,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
return; return;
} }
if (m.getStatus() == Message.STATUS_RECEIVED && t != null && (t.getStatus() == Transferable.STATUS_CANCELLED || t.getStatus() == Transferable.STATUS_FAILED)) {
return;
}
final boolean deleted = m.isDeleted(); final boolean deleted = m.isDeleted();
final boolean encrypted = m.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED final boolean encrypted = m.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED
|| m.getEncryption() == Message.ENCRYPTION_PGP; || m.getEncryption() == Message.ENCRYPTION_PGP;

View File

@ -431,7 +431,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
bookmark.setConversation(conversation); bookmark.setConversation(conversation);
if (!bookmark.autojoin() && getPreferences().getBoolean("autojoin", getResources().getBoolean(R.bool.autojoin))) { if (!bookmark.autojoin() && getPreferences().getBoolean("autojoin", getResources().getBoolean(R.bool.autojoin))) {
bookmark.setAutojoin(true); bookmark.setAutojoin(true);
xmppConnectionService.pushBookmarks(bookmark.getAccount()); xmppConnectionService.createBookmark(bookmark.getAccount(), bookmark);
} }
SoftKeyboardUtils.hideSoftKeyboard(this); SoftKeyboardUtils.hideSoftKeyboard(this);
switchToConversation(conversation); switchToConversation(conversation);
@ -478,9 +478,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
builder.setMessage(JidDialog.style(this, R.string.remove_bookmark_text, bookmark.getJid().toEscapedString())); builder.setMessage(JidDialog.style(this, R.string.remove_bookmark_text, bookmark.getJid().toEscapedString()));
builder.setPositiveButton(R.string.delete, (dialog, which) -> { builder.setPositiveButton(R.string.delete, (dialog, which) -> {
bookmark.setConversation(null); bookmark.setConversation(null);
Account account = bookmark.getAccount(); final Account account = bookmark.getAccount();
account.getBookmarks().remove(bookmark); xmppConnectionService.deleteBookmark(account, bookmark);
xmppConnectionService.pushBookmarks(account);
filter(mSearchEditText.getText().toString()); filter(mSearchEditText.getText().toString());
}); });
builder.create().show(); builder.create().show();
@ -1041,8 +1040,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
if (nick != null && !nick.isEmpty() && !nick.equals(MucOptions.defaultNick(account))) { if (nick != null && !nick.isEmpty() && !nick.equals(MucOptions.defaultNick(account))) {
bookmark.setNick(nick); bookmark.setNick(nick);
} }
account.getBookmarks().add(bookmark); xmppConnectionService.createBookmark(account, bookmark);
xmppConnectionService.pushBookmarks(account);
final Conversation conversation = xmppConnectionService final Conversation conversation = xmppConnectionService
.findOrCreateConversation(account, conferenceJid, true, true, true); .findOrCreateConversation(account, conferenceJid, true, true, true);
bookmark.setConversation(conversation); bookmark.setConversation(conversation);

View File

@ -1,17 +1,21 @@
package eu.siacs.conversations.ui.adapter; package eu.siacs.conversations.ui.adapter;
import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.res.Resources; import android.content.res.Resources;
import android.databinding.DataBindingUtil; import android.databinding.DataBindingUtil;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.Toast;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
@ -20,6 +24,7 @@ import java.util.concurrent.RejectedExecutionException;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.MediaPreviewBinding; import eu.siacs.conversations.databinding.MediaPreviewBinding;
import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.ui.ConversationFragment; import eu.siacs.conversations.ui.ConversationFragment;
import eu.siacs.conversations.ui.XmppActivity; import eu.siacs.conversations.ui.XmppActivity;
import eu.siacs.conversations.ui.util.Attachment; import eu.siacs.conversations.ui.util.Attachment;
@ -54,11 +59,24 @@ public class MediaPreviewAdapter extends RecyclerView.Adapter<MediaPreviewAdapte
MediaAdapter.renderPreview(context, attachment, holder.binding.mediaPreview); MediaAdapter.renderPreview(context, attachment, holder.binding.mediaPreview);
} }
holder.binding.deleteButton.setOnClickListener(v -> { holder.binding.deleteButton.setOnClickListener(v -> {
int pos = mediaPreviews.indexOf(attachment); final int pos = mediaPreviews.indexOf(attachment);
mediaPreviews.remove(pos); mediaPreviews.remove(pos);
notifyItemRemoved(pos); notifyItemRemoved(pos);
conversationFragment.toggleInputMethod(); conversationFragment.toggleInputMethod();
}); });
holder.binding.mediaPreview.setOnClickListener(v -> view(context, attachment));
}
private static void view(final Context context, Attachment attachment) {
final Intent view = new Intent(Intent.ACTION_VIEW);
final Uri uri = FileBackend.getUriForUri(context, attachment.getUri());
view.setDataAndType(uri, attachment.getMime());
view.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
try {
context.startActivity(view);
} catch (ActivityNotFoundException e) {
Toast.makeText(context, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT).show();
}
} }
public void addMediaPreviews(List<Attachment> attachments) { public void addMediaPreviews(List<Attachment> attachments) {

View File

@ -187,7 +187,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
if (message.isFileOrImage() || transferable != null) { if (message.isFileOrImage() || transferable != null) {
FileParams params = message.getFileParams(); FileParams params = message.getFileParams();
filesize = params.size > 0 ? UIHelper.filesizeToString(params.size) : null; filesize = params.size > 0 ? UIHelper.filesizeToString(params.size) : null;
if (transferable != null && transferable.getStatus() == Transferable.STATUS_FAILED) { if (transferable != null && (transferable.getStatus() == Transferable.STATUS_FAILED || transferable.getStatus() == Transferable.STATUS_CANCELLED)) {
error = true; error = true;
} }
} }
@ -206,10 +206,6 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
info = getContext().getString(R.string.offering); info = getContext().getString(R.string.offering);
break; break;
case Message.STATUS_SEND_RECEIVED: case Message.STATUS_SEND_RECEIVED:
if (mIndicateReceived) {
viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
}
break;
case Message.STATUS_SEND_DISPLAYED: case Message.STATUS_SEND_DISPLAYED:
if (mIndicateReceived) { if (mIndicateReceived) {
viewHolder.indicatorReceived.setVisibility(View.VISIBLE); viewHolder.indicatorReceived.setVisibility(View.VISIBLE);

View File

@ -53,11 +53,10 @@ public class ShareUtil {
if (message.isGeoUri()) { if (message.isGeoUri()) {
shareIntent.putExtra(Intent.EXTRA_TEXT, message.getBody()); shareIntent.putExtra(Intent.EXTRA_TEXT, message.getBody());
shareIntent.setType("text/plain"); shareIntent.setType("text/plain");
shareIntent.putExtra(ConversationsActivity.EXTRA_AS_QUOTE, true);
} else if (!message.isFileOrImage()) { } else if (!message.isFileOrImage()) {
shareIntent.putExtra(Intent.EXTRA_TEXT, message.getMergedBody().toString()); shareIntent.putExtra(Intent.EXTRA_TEXT, message.getMergedBody().toString());
shareIntent.setType("text/plain"); shareIntent.setType("text/plain");
shareIntent.putExtra(ConversationsActivity.EXTRA_AS_QUOTE, true); shareIntent.putExtra(ConversationsActivity.EXTRA_AS_QUOTE, message.getStatus() == Message.STATUS_RECEIVED);
} else { } else {
final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message); final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
try { try {

View File

@ -14,23 +14,25 @@ public class SocksSocketFactory {
private static final byte[] LOCALHOST = new byte[]{127,0,0,1}; private static final byte[] LOCALHOST = new byte[]{127,0,0,1};
public static void createSocksConnection(Socket socket, String destination, int port) throws IOException { public static void createSocksConnection(final Socket socket, final String destination, final int port) throws IOException {
InputStream proxyIs = socket.getInputStream(); final InputStream proxyIs = socket.getInputStream();
OutputStream proxyOs = socket.getOutputStream(); final OutputStream proxyOs = socket.getOutputStream();
proxyOs.write(new byte[]{0x05, 0x01, 0x00}); proxyOs.write(new byte[]{0x05, 0x01, 0x00});
byte[] response = new byte[2]; proxyOs.flush();
proxyIs.read(response); final byte[] handshake = new byte[2];
if (response[0] != 0x05 || response[1] != 0x00) { proxyIs.read(handshake);
if (handshake[0] != 0x05 || handshake[1] != 0x00) {
throw new SocksConnectionException("Socks 5 handshake failed"); throw new SocksConnectionException("Socks 5 handshake failed");
} }
byte[] dest = destination.getBytes(); final byte[] dest = destination.getBytes();
ByteBuffer request = ByteBuffer.allocate(7 + dest.length); final ByteBuffer request = ByteBuffer.allocate(7 + dest.length);
request.put(new byte[]{0x05, 0x01, 0x00, 0x03}); request.put(new byte[]{0x05, 0x01, 0x00, 0x03});
request.put((byte) dest.length); request.put((byte) dest.length);
request.put(dest); request.put(dest);
request.putShort((short) port); request.putShort((short) port);
proxyOs.write(request.array()); proxyOs.write(request.array());
response = new byte[7 + dest.length]; proxyOs.flush();
final byte[] response = new byte[7 + dest.length];
proxyIs.read(response); proxyIs.read(response);
if (response[1] != 0x00) { if (response[1] != 0x00) {
if (response[1] == 0x04) { if (response[1] == 0x04) {
@ -52,7 +54,7 @@ public class SocksSocketFactory {
return false; return false;
} }
public static Socket createSocket(InetSocketAddress address, String destination, int port) throws IOException { private static Socket createSocket(InetSocketAddress address, String destination, int port) throws IOException {
Socket socket = new Socket(); Socket socket = new Socket();
try { try {
socket.connect(address, Config.CONNECT_TIMEOUT * 1000); socket.connect(address, Config.CONNECT_TIMEOUT * 1000);

View File

@ -274,6 +274,8 @@ public class UIHelper {
getFileDescriptionString(context, message)), true); getFileDescriptionString(context, message)), true);
case Transferable.STATUS_FAILED: case Transferable.STATUS_FAILED:
return new Pair<>(context.getString(R.string.file_transmission_failed), true); return new Pair<>(context.getString(R.string.file_transmission_failed), true);
case Transferable.STATUS_CANCELLED:
return new Pair<>(context.getString(R.string.file_transmission_cancelled), true);
case Transferable.STATUS_UPLOADING: case Transferable.STATUS_UPLOADING:
if (message.getStatus() == Message.STATUS_OFFERED) { if (message.getStatus() == Message.STATUS_OFFERED) {
return new Pair<>(context.getString(R.string.offering_x_file, return new Pair<>(context.getString(R.string.offering_x_file,

View File

@ -33,4 +33,6 @@ public final class Namespace {
public static final String JINGLE_ENCRYPTED_TRANSPORT = "urn:xmpp:jingle:jet:0"; public static final String JINGLE_ENCRYPTED_TRANSPORT = "urn:xmpp:jingle:jet:0";
public static final String JINGLE_ENCRYPTED_TRANSPORT_OMEMO = "urn:xmpp:jingle:jet-omemo:0"; public static final String JINGLE_ENCRYPTED_TRANSPORT_OMEMO = "urn:xmpp:jingle:jet-omemo:0";
public static final String MUC_USER = "http://jabber.org/protocol/muc#user"; public static final String MUC_USER = "http://jabber.org/protocol/muc#user";
public static final String BOOKMARKS2 = "urn:xmpp:bookmarks:0";
public static final String BOOKMARKS2_COMPAT = BOOKMARKS2+"#compat";
} }

View File

@ -1857,5 +1857,9 @@ public class XmppConnection implements Runnable {
public boolean stanzaIds() { public boolean stanzaIds() {
return hasDiscoFeature(account.getJid().asBareJid(), Namespace.STANZA_IDS); return hasDiscoFeature(account.getJid().asBareJid(), Namespace.STANZA_IDS);
} }
public boolean bookmarks2() {
return Config.USE_BOOKMARKS2 /* || hasDiscoFeature(account.getJid().asBareJid(), Namespace.BOOKMARKS2_COMPAT)*/;
}
} }
} }

View File

@ -142,7 +142,7 @@ public class JingleConnection implements Transferable {
@Override @Override
public void onFileTransferAborted() { public void onFileTransferAborted() {
JingleConnection.this.sendCancel(); JingleConnection.this.sendSessionTerminate("connectivity-error");
JingleConnection.this.fail(); JingleConnection.this.fail();
} }
}; };
@ -222,27 +222,32 @@ public class JingleConnection implements Transferable {
return this.message.getCounterpart(); return this.message.getCounterpart();
} }
public void deliverPacket(JinglePacket packet) { void deliverPacket(JinglePacket packet) {
boolean returnResult = true;
if (packet.isAction("session-terminate")) { if (packet.isAction("session-terminate")) {
Reason reason = packet.getReason(); Reason reason = packet.getReason();
if (reason != null) { if (reason != null) {
if (reason.hasChild("cancel")) { if (reason.hasChild("cancel")) {
this.cancelled = true;
this.fail(); this.fail();
} else if (reason.hasChild("success")) { } else if (reason.hasChild("success")) {
this.receiveSuccess(); this.receiveSuccess();
} else { } else {
this.fail(); final List<Element> children = reason.getChildren();
if (children.size() == 1) {
this.fail(children.get(0).getName());
} else {
this.fail();
}
} }
} else { } else {
this.fail(); this.fail();
} }
} else if (packet.isAction("session-accept")) { } else if (packet.isAction("session-accept")) {
returnResult = receiveAccept(packet); receiveAccept(packet);
} else if (packet.isAction("session-info")) { } else if (packet.isAction("session-info")) {
Element checksum = packet.getChecksum(); final Element checksum = packet.getChecksum();
Element file = checksum == null ? null : checksum.findChild("file"); final Element file = checksum == null ? null : checksum.findChild("file");
Element hash = file == null ? null : file.findChild("hash", "urn:xmpp:hashes:2"); final Element hash = file == null ? null : file.findChild("hash", "urn:xmpp:hashes:2");
if (hash != null && "sha-1".equalsIgnoreCase(hash.getAttribute("algo"))) { if (hash != null && "sha-1".equalsIgnoreCase(hash.getAttribute("algo"))) {
try { try {
this.expectedHash = Base64.decode(hash.getContent(), Base64.DEFAULT); this.expectedHash = Base64.decode(hash.getContent(), Base64.DEFAULT);
@ -250,33 +255,44 @@ public class JingleConnection implements Transferable {
this.expectedHash = new byte[0]; this.expectedHash = new byte[0];
} }
} }
respondToIq(packet, true);
} else if (packet.isAction("transport-info")) { } else if (packet.isAction("transport-info")) {
returnResult = receiveTransportInfo(packet); receiveTransportInfo(packet);
} else if (packet.isAction("transport-replace")) { } else if (packet.isAction("transport-replace")) {
if (packet.getJingleContent().hasIbbTransport()) { if (packet.getJingleContent().hasIbbTransport()) {
returnResult = this.receiveFallbackToIbb(packet); receiveFallbackToIbb(packet);
} else { } else {
returnResult = false; Log.d(Config.LOGTAG, "trying to fallback to something unknown" + packet.toString());
Log.d(Config.LOGTAG, "trying to fallback to something unknown" respondToIq(packet, false);
+ packet.toString());
} }
} else if (packet.isAction("transport-accept")) { } else if (packet.isAction("transport-accept")) {
returnResult = this.receiveTransportAccept(packet); receiveTransportAccept(packet);
} else { } else {
Log.d(Config.LOGTAG, "packet arrived in connection. action was " Log.d(Config.LOGTAG, "packet arrived in connection. action was " + packet.getAction());
+ packet.getAction()); respondToIq(packet, false);
returnResult = false;
} }
IqPacket response; }
if (returnResult) {
response = packet.generateResponse(IqPacket.TYPE.RESULT);
private void respondToIq(final IqPacket packet, final boolean result) {
final IqPacket response;
if (result) {
response = packet.generateResponse(IqPacket.TYPE.RESULT);
} else { } else {
response = packet.generateResponse(IqPacket.TYPE.ERROR); response = packet.generateResponse(IqPacket.TYPE.ERROR);
final Element error = response.addChild("error").setAttribute("type", "cancel");
error.addChild("not-acceptable", "urn:ietf:params:xml:ns:xmpp-stanzas");
} }
mXmppConnectionService.sendIqPacket(account, response, null); mXmppConnectionService.sendIqPacket(account, response, null);
} }
private void respondToIqWithOutOfOrder(final IqPacket packet) {
final IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR);
final Element error = response.addChild("error").setAttribute("type", "wait");
error.addChild("unexpected-request", "urn:ietf:params:xml:ns:xmpp-stanzas");
error.addChild("out-of-order", "urn:xmpp:jingle:errors:1");
mXmppConnectionService.sendIqPacket(account, response, null);
}
public void init(final Message message) { public void init(final Message message) {
if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
Conversation conversation = (Conversation) message.getConversation(); Conversation conversation = (Conversation) message.getConversation();
@ -320,7 +336,7 @@ public class JingleConnection implements Transferable {
@Override @Override
public void failed() { public void failed() {
Log.d(Config.LOGTAG, "connection to our own proxy65 candidate failed"); Log.d(Config.LOGTAG, String.format("connection to our own proxy65 candidate failed (%s:%d)", candidate.getHost(), candidate.getPort()));
sendInitRequest(); sendInitRequest();
} }
@ -400,7 +416,6 @@ public class JingleConnection implements Transferable {
this.contentName = content.getAttribute("name"); this.contentName = content.getAttribute("name");
this.transportId = content.getTransportId(); this.transportId = content.getTransportId();
mXmppConnectionService.sendIqPacket(account, packet.generateResponse(IqPacket.TYPE.RESULT), null);
if (this.initialTransport == Transport.SOCKS) { if (this.initialTransport == Transport.SOCKS) {
this.mergeCandidates(JingleCandidate.parse(content.socks5transport().getChildren())); this.mergeCandidates(JingleCandidate.parse(content.socks5transport().getChildren()));
@ -411,20 +426,20 @@ public class JingleConnection implements Transferable {
this.ibbBlockSize = Math.min(Integer.parseInt(receivedBlockSize), this.ibbBlockSize); this.ibbBlockSize = Math.min(Integer.parseInt(receivedBlockSize), this.ibbBlockSize);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
Log.d(Config.LOGTAG, "number format exception " + e.getMessage()); Log.d(Config.LOGTAG, "number format exception " + e.getMessage());
this.sendCancel(); respondToIq(packet, false);
this.fail(); this.fail();
return; return;
} }
} else { } else {
Log.d(Config.LOGTAG, "received block size was null"); Log.d(Config.LOGTAG, "received block size was null");
this.sendCancel(); respondToIq(packet, false);
this.fail(); this.fail();
return; return;
} }
} }
this.ftVersion = content.getVersion(); this.ftVersion = content.getVersion();
if (ftVersion == null) { if (ftVersion == null) {
this.sendCancel(); respondToIq(packet, false);
this.fail(); this.fail();
return; return;
} }
@ -486,6 +501,9 @@ public class JingleConnection implements Transferable {
//JET reports the plain text size. however lower levels of our receiving code still //JET reports the plain text size. however lower levels of our receiving code still
//expect the cipher text size. so we just + 16 bytes (auth tag size) here //expect the cipher text size. so we just + 16 bytes (auth tag size) here
this.file.setExpectedSize(size + (remoteIsUsingJet ? 16 : 0)); this.file.setExpectedSize(size + (remoteIsUsingJet ? 16 : 0));
respondToIq(packet, true);
if (mJingleConnectionManager.hasStoragePermission() if (mJingleConnectionManager.hasStoragePermission()
&& size < this.mJingleConnectionManager.getAutoAcceptFileSize() && size < this.mJingleConnectionManager.getAutoAcceptFileSize()
&& mXmppConnectionService.isDataSaverDisabled()) { && mXmppConnectionService.isDataSaverDisabled()) {
@ -503,13 +521,9 @@ public class JingleConnection implements Transferable {
this.mXmppConnectionService.getNotificationService().push(message); this.mXmppConnectionService.getNotificationService().push(message);
} }
Log.d(Config.LOGTAG, "receiving file: expecting size of " + this.file.getExpectedSize()); Log.d(Config.LOGTAG, "receiving file: expecting size of " + this.file.getExpectedSize());
} else { return;
this.sendCancel();
this.fail();
} }
} else { respondToIq(packet, false);
this.sendCancel();
this.fail();
} }
} }
@ -557,14 +571,17 @@ public class JingleConnection implements Transferable {
try { try {
this.mFileInputStream = new FileInputStream(file); this.mFileInputStream = new FileInputStream(file);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
abort(); fail(e.getMessage());
return; return;
} }
content.setTransportId(this.transportId); content.setTransportId(this.transportId);
if (this.initialTransport == Transport.IBB) { if (this.initialTransport == Transport.IBB) {
content.ibbTransport().setAttribute("block-size", Integer.toString(this.ibbBlockSize)); content.ibbTransport().setAttribute("block-size", Integer.toString(this.ibbBlockSize));
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": sending IBB offer");
} else { } else {
content.socks5transport().setChildren(getCandidatesAsElements()); final List<Element> candidates = getCandidatesAsElements();
Log.d(Config.LOGTAG, String.format("%s: sending S5B offer with %d candidates", account.getJid().asBareJid(), candidates.size()));
content.socks5transport().setChildren(candidates);
} }
packet.setContent(content); packet.setContent(content);
this.sendJinglePacket(packet, (account, response) -> { this.sendJinglePacket(packet, (account, response) -> {
@ -682,18 +699,19 @@ public class JingleConnection implements Transferable {
mXmppConnectionService.sendIqPacket(account, packet, callback); mXmppConnectionService.sendIqPacket(account, packet, callback);
} }
private boolean receiveAccept(JinglePacket packet) { private void receiveAccept(JinglePacket packet) {
if (this.mJingleStatus != JINGLE_STATUS_INITIATED) { if (this.mJingleStatus != JINGLE_STATUS_INITIATED) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received out of order session-accept"); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received out of order session-accept");
return false; respondToIqWithOutOfOrder(packet);
return;
} }
this.mJingleStatus = JINGLE_STATUS_ACCEPTED; this.mJingleStatus = JINGLE_STATUS_ACCEPTED;
mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND); mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND);
Content content = packet.getJingleContent(); Content content = packet.getJingleContent();
if (content.hasSocks5Transport()) { if (content.hasSocks5Transport()) {
respondToIq(packet, true);
mergeCandidates(JingleCandidate.parse(content.socks5transport().getChildren())); mergeCandidates(JingleCandidate.parse(content.socks5transport().getChildren()));
this.connectNextCandidate(); this.connectNextCandidate();
return true;
} else if (content.hasIbbTransport()) { } else if (content.hasIbbTransport()) {
String receivedBlockSize = packet.getJingleContent().ibbTransport().getAttribute("block-size"); String receivedBlockSize = packet.getJingleContent().ibbTransport().getAttribute("block-size");
if (receivedBlockSize != null) { if (receivedBlockSize != null) {
@ -706,18 +724,19 @@ public class JingleConnection implements Transferable {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to parse block size in session-accept"); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to parse block size in session-accept");
} }
} }
respondToIq(packet, true);
this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize); this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize);
this.transport.connect(onIbbTransportConnected); this.transport.connect(onIbbTransportConnected);
return true;
} else { } else {
return false; respondToIq(packet, false);
} }
} }
private boolean receiveTransportInfo(JinglePacket packet) { private void receiveTransportInfo(JinglePacket packet) {
Content content = packet.getJingleContent(); final Content content = packet.getJingleContent();
if (content.hasSocks5Transport()) { if (content.hasSocks5Transport()) {
if (content.socks5transport().hasChild("activated")) { if (content.socks5transport().hasChild("activated")) {
respondToIq(packet, true);
if ((this.transport != null) && (this.transport instanceof JingleSocks5Transport)) { if ((this.transport != null) && (this.transport instanceof JingleSocks5Transport)) {
onProxyActivated.success(); onProxyActivated.success();
} else { } else {
@ -729,21 +748,20 @@ public class JingleConnection implements Transferable {
connection.setActivated(true); connection.setActivated(true);
} else { } else {
Log.d(Config.LOGTAG, "activated connection not found"); Log.d(Config.LOGTAG, "activated connection not found");
this.sendCancel(); sendSessionTerminate("failed-transport");
this.fail(); this.fail();
} }
} }
return true;
} else if (content.socks5transport().hasChild("proxy-error")) { } else if (content.socks5transport().hasChild("proxy-error")) {
respondToIq(packet, true);
onProxyActivated.failed(); onProxyActivated.failed();
return true;
} else if (content.socks5transport().hasChild("candidate-error")) { } else if (content.socks5transport().hasChild("candidate-error")) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received candidate error"); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received candidate error");
respondToIq(packet, true);
this.receivedCandidate = true; this.receivedCandidate = true;
if (mJingleStatus == JINGLE_STATUS_ACCEPTED && this.sentCandidate) { if (mJingleStatus == JINGLE_STATUS_ACCEPTED && this.sentCandidate) {
this.connect(); this.connect();
} }
return true;
} else if (content.socks5transport().hasChild("candidate-used")) { } else if (content.socks5transport().hasChild("candidate-used")) {
String cid = content.socks5transport().findChild("candidate-used").getAttribute("cid"); String cid = content.socks5transport().findChild("candidate-used").getAttribute("cid");
if (cid != null) { if (cid != null) {
@ -751,8 +769,10 @@ public class JingleConnection implements Transferable {
JingleCandidate candidate = getCandidate(cid); JingleCandidate candidate = getCandidate(cid);
if (candidate == null) { if (candidate == null) {
Log.d(Config.LOGTAG, "could not find candidate with cid=" + cid); Log.d(Config.LOGTAG, "could not find candidate with cid=" + cid);
return false; respondToIq(packet, false);
return;
} }
respondToIq(packet, true);
candidate.flagAsUsedByCounterpart(); candidate.flagAsUsedByCounterpart();
this.receivedCandidate = true; this.receivedCandidate = true;
if (mJingleStatus == JINGLE_STATUS_ACCEPTED && this.sentCandidate) { if (mJingleStatus == JINGLE_STATUS_ACCEPTED && this.sentCandidate) {
@ -760,15 +780,14 @@ public class JingleConnection implements Transferable {
} else { } else {
Log.d(Config.LOGTAG, "ignoring because file is already in transmission or we haven't sent our candidate yet status=" + mJingleStatus + " sentCandidate=" + sentCandidate); Log.d(Config.LOGTAG, "ignoring because file is already in transmission or we haven't sent our candidate yet status=" + mJingleStatus + " sentCandidate=" + sentCandidate);
} }
return true;
} else { } else {
return false; respondToIq(packet, false);
} }
} else { } else {
return false; respondToIq(packet, false);
} }
} else { } else {
return true; respondToIq(packet, true);
} }
} }
@ -867,11 +886,7 @@ public class JingleConnection implements Transferable {
} }
private void sendSuccess() { private void sendSuccess() {
JinglePacket packet = bootstrapPacket("session-terminate"); sendSessionTerminate("success");
Reason reason = new Reason();
reason.addChild("success");
packet.setReason(reason);
this.sendJinglePacket(packet);
this.disconnectSocks5Connections(); this.disconnectSocks5Connections();
this.mJingleStatus = JINGLE_STATUS_FINISHED; this.mJingleStatus = JINGLE_STATUS_FINISHED;
this.message.setStatus(Message.STATUS_RECEIVED); this.message.setStatus(Message.STATUS_RECEIVED);
@ -893,7 +908,7 @@ public class JingleConnection implements Transferable {
} }
private boolean receiveFallbackToIbb(JinglePacket packet) { private void receiveFallbackToIbb(JinglePacket packet) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": receiving fallback to ibb"); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": receiving fallback to ibb");
final String receivedBlockSize = packet.getJingleContent().ibbTransport().getAttribute("block-size"); final String receivedBlockSize = packet.getJingleContent().ibbTransport().getAttribute("block-size");
if (receivedBlockSize != null) { if (receivedBlockSize != null) {
@ -916,6 +931,7 @@ public class JingleConnection implements Transferable {
content.ibbTransport().setAttribute("sid", this.transportId); content.ibbTransport().setAttribute("sid", this.transportId);
answer.setContent(content); answer.setContent(content);
respondToIq(packet, true);
if (initiating()) { if (initiating()) {
this.sendJinglePacket(answer, (account, response) -> { this.sendJinglePacket(answer, (account, response) -> {
@ -928,13 +944,13 @@ public class JingleConnection implements Transferable {
this.transport.receive(file, onFileTransmissionStatusChanged); this.transport.receive(file, onFileTransmissionStatusChanged);
this.sendJinglePacket(answer); this.sendJinglePacket(answer);
} }
return true;
} }
private boolean receiveTransportAccept(JinglePacket packet) { private void receiveTransportAccept(JinglePacket packet) {
if (packet.getJingleContent().hasIbbTransport()) { if (packet.getJingleContent().hasIbbTransport()) {
String receivedBlockSize = packet.getJingleContent().ibbTransport() final Element ibbTransport = packet.getJingleContent().ibbTransport();
.getAttribute("block-size"); final String receivedBlockSize = ibbTransport.getAttribute("block-size");
final String sid = ibbTransport.getAttribute("sid");
if (receivedBlockSize != null) { if (receivedBlockSize != null) {
try { try {
int bs = Integer.parseInt(receivedBlockSize); int bs = Integer.parseInt(receivedBlockSize);
@ -947,15 +963,19 @@ public class JingleConnection implements Transferable {
} }
this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize); this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize);
if (sid == null || !sid.equals(this.transportId)) {
Log.w(Config.LOGTAG, String.format("%s: sid in transport-accept (%s) did not match our sid (%s) ", account.getJid().asBareJid(), sid, transportId));
}
respondToIq(packet, true);
//might be receive instead if we are not initiating //might be receive instead if we are not initiating
if (initiating()) { if (initiating()) {
this.transport.connect(onIbbTransportConnected); this.transport.connect(onIbbTransportConnected);
} else { } else {
this.transport.receive(file, onFileTransmissionStatusChanged); this.transport.receive(file, onFileTransmissionStatusChanged);
} }
return true;
} else { } else {
return false; Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received invalid transport-accept");
respondToIq(packet, false);
} }
} }
@ -977,18 +997,18 @@ public class JingleConnection implements Transferable {
@Override @Override
public void cancel() { public void cancel() {
this.cancelled = true; this.cancelled = true;
abort(); abort("cancel");
} }
public void abort() { void abort(final String reason) {
this.disconnectSocks5Connections(); this.disconnectSocks5Connections();
if (this.transport instanceof JingleInbandTransport) { if (this.transport instanceof JingleInbandTransport) {
this.transport.disconnect(); this.transport.disconnect();
} }
this.sendCancel(); sendSessionTerminate(reason);
this.mJingleConnectionManager.finishConnection(this); this.mJingleConnectionManager.finishConnection(this);
if (responding()) { if (responding()) {
this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED)); this.message.setTransferable(new TransferablePlaceholder(cancelled ? Transferable.STATUS_CANCELLED : Transferable.STATUS_FAILED));
if (this.file != null) { if (this.file != null) {
file.delete(); file.delete();
} }
@ -1013,7 +1033,7 @@ public class JingleConnection implements Transferable {
FileBackend.close(mFileOutputStream); FileBackend.close(mFileOutputStream);
if (this.message != null) { if (this.message != null) {
if (responding()) { if (responding()) {
this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED)); this.message.setTransferable(new TransferablePlaceholder(cancelled ? Transferable.STATUS_CANCELLED : Transferable.STATUS_FAILED));
if (this.file != null) { if (this.file != null) {
file.delete(); file.delete();
} }
@ -1028,11 +1048,11 @@ public class JingleConnection implements Transferable {
this.mJingleConnectionManager.finishConnection(this); this.mJingleConnectionManager.finishConnection(this);
} }
private void sendCancel() { private void sendSessionTerminate(String reason) {
JinglePacket packet = bootstrapPacket("session-terminate"); final JinglePacket packet = bootstrapPacket("session-terminate");
Reason reason = new Reason(); final Reason r = new Reason();
reason.addChild("cancel"); r.addChild(reason);
packet.setReason(reason); packet.setReason(r);
this.sendJinglePacket(packet); this.sendJinglePacket(packet);
} }

View File

@ -106,7 +106,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
candidate.setPort(Integer.parseInt(port)); candidate.setPort(Integer.parseInt(port));
candidate.setType(JingleCandidate.TYPE_PROXY); candidate.setType(JingleCandidate.TYPE_PROXY);
candidate.setJid(proxy); candidate.setJid(proxy);
candidate.setPriority(655360 + (initiator ? 10 : 20)); candidate.setPriority(655360 + (initiator ? 30 : 0));
primaryCandidates.put(account.getJid().asBareJid(),candidate); primaryCandidates.put(account.getJid().asBareJid(),candidate);
listener.onPrimaryCandidateFound(true,candidate); listener.onPrimaryCandidateFound(true,candidate);
} catch (final NumberFormatException e) { } catch (final NumberFormatException e) {
@ -166,7 +166,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
public void cancelInTransmission() { public void cancelInTransmission() {
for (JingleConnection connection : this.connections) { for (JingleConnection connection : this.connections) {
if (connection.getJingleStatus() == JingleConnection.JINGLE_STATUS_TRANSMITTING) { if (connection.getJingleStatus() == JingleConnection.JINGLE_STATUS_TRANSMITTING) {
connection.abort(); connection.abort("connectivity-error");
} }
} }
} }

View File

@ -23,223 +23,222 @@ import rocks.xmpp.addr.Jid;
public class JingleInbandTransport extends JingleTransport { public class JingleInbandTransport extends JingleTransport {
private Account account; private Account account;
private Jid counterpart; private Jid counterpart;
private int blockSize; private int blockSize;
private int seq = 0; private int seq = 0;
private String sessionId; private String sessionId;
private boolean established = false; private boolean established = false;
private boolean connected = true; private boolean connected = true;
private DownloadableFile file; private DownloadableFile file;
private JingleConnection connection; private JingleConnection connection;
private InputStream fileInputStream = null; private InputStream fileInputStream = null;
private InputStream innerInputStream = null; private InputStream innerInputStream = null;
private OutputStream fileOutputStream = null; private OutputStream fileOutputStream = null;
private long remainingSize = 0; private long remainingSize = 0;
private long fileSize = 0; private long fileSize = 0;
private MessageDigest digest; private MessageDigest digest;
private OnFileTransmissionStatusChanged onFileTransmissionStatusChanged; private OnFileTransmissionStatusChanged onFileTransmissionStatusChanged;
private OnIqPacketReceived onAckReceived = new OnIqPacketReceived() { private OnIqPacketReceived onAckReceived = new OnIqPacketReceived() {
@Override @Override
public void onIqPacketReceived(Account account, IqPacket packet) { public void onIqPacketReceived(Account account, IqPacket packet) {
if (connected && packet.getType() == IqPacket.TYPE.RESULT) { if (!connected) {
if (remainingSize > 0) { return;
sendNextBlock(); }
} if (packet.getType() == IqPacket.TYPE.RESULT) {
} if (remainingSize > 0) {
} sendNextBlock();
}; }
} else if (packet.getType() == IqPacket.TYPE.ERROR) {
onFileTransmissionStatusChanged.onFileTransferAborted();
}
}
};
public JingleInbandTransport(final JingleConnection connection, final String sid, final int blocksize) { public JingleInbandTransport(final JingleConnection connection, final String sid, final int blocksize) {
this.connection = connection; this.connection = connection;
this.account = connection.getAccount(); this.account = connection.getAccount();
this.counterpart = connection.getCounterPart(); this.counterpart = connection.getCounterPart();
this.blockSize = blocksize; this.blockSize = blocksize;
this.sessionId = sid; this.sessionId = sid;
}
private void sendClose() {
IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
iq.setTo(this.counterpart);
Element close = iq.addChild("close", "http://jabber.org/protocol/ibb");
close.setAttribute("sid", this.sessionId);
this.account.getXmppConnection().sendIqPacket(iq, null);
}
public void connect(final OnTransportConnected callback) {
IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
iq.setTo(this.counterpart);
Element open = iq.addChild("open", "http://jabber.org/protocol/ibb");
open.setAttribute("sid", this.sessionId);
open.setAttribute("stanza", "iq");
open.setAttribute("block-size", Integer.toString(this.blockSize));
this.connected = true;
this.account.getXmppConnection().sendIqPacket(iq,
new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account,
IqPacket packet) {
if (packet.getType() != IqPacket.TYPE.RESULT) {
callback.failed();
} else {
callback.established();
}
}
});
}
@Override
public void receive(DownloadableFile file, OnFileTransmissionStatusChanged callback) {
this.onFileTransmissionStatusChanged = callback;
this.file = file;
try {
this.digest = MessageDigest.getInstance("SHA-1");
digest.reset();
this.fileOutputStream = connection.getFileOutputStream();
if (this.fileOutputStream == null) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": could not create output stream");
callback.onFileTransferAborted();
return;
}
this.remainingSize = this.fileSize = file.getExpectedSize();
} catch (final NoSuchAlgorithmException | IOException e) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+" "+e.getMessage());
callback.onFileTransferAborted();
}
} }
@Override private void sendClose() {
public void send(DownloadableFile file, OnFileTransmissionStatusChanged callback) { IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
this.onFileTransmissionStatusChanged = callback; iq.setTo(this.counterpart);
this.file = file; Element close = iq.addChild("close", "http://jabber.org/protocol/ibb");
try { close.setAttribute("sid", this.sessionId);
this.remainingSize = this.file.getExpectedSize(); this.account.getXmppConnection().sendIqPacket(iq, null);
this.fileSize = this.remainingSize; }
this.digest = MessageDigest.getInstance("SHA-1");
this.digest.reset();
fileInputStream = connection.getFileInputStream();
if (fileInputStream == null) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": could no create input stream");
callback.onFileTransferAborted();
return;
}
innerInputStream = AbstractConnectionManager.upgrade(file, fileInputStream);
if (this.connected) {
this.sendNextBlock();
}
} catch (Exception e) {
callback.onFileTransferAborted();
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": "+e.getMessage());
}
}
@Override public void connect(final OnTransportConnected callback) {
public void disconnect() { IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
this.connected = false; iq.setTo(this.counterpart);
FileBackend.close(fileOutputStream); Element open = iq.addChild("open", "http://jabber.org/protocol/ibb");
FileBackend.close(fileInputStream); open.setAttribute("sid", this.sessionId);
} open.setAttribute("stanza", "iq");
open.setAttribute("block-size", Integer.toString(this.blockSize));
this.connected = true;
this.account.getXmppConnection().sendIqPacket(iq, (account, packet) -> {
if (packet.getType() != IqPacket.TYPE.RESULT) {
callback.failed();
} else {
callback.established();
}
});
}
private void sendNextBlock() { @Override
byte[] buffer = new byte[this.blockSize]; public void receive(DownloadableFile file, OnFileTransmissionStatusChanged callback) {
try { this.onFileTransmissionStatusChanged = callback;
int count = innerInputStream.read(buffer); this.file = file;
if (count == -1) { try {
sendClose(); this.digest = MessageDigest.getInstance("SHA-1");
file.setSha1Sum(digest.digest()); digest.reset();
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": sendNextBlock() count was -1"); this.fileOutputStream = connection.getFileOutputStream();
this.onFileTransmissionStatusChanged.onFileTransmitted(file); if (this.fileOutputStream == null) {
fileInputStream.close(); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": could not create output stream");
return; callback.onFileTransferAborted();
} else if (count != buffer.length) { return;
int rem = innerInputStream.read(buffer,count,buffer.length-count); }
if (rem > 0) { this.remainingSize = this.fileSize = file.getExpectedSize();
count += rem; } catch (final NoSuchAlgorithmException | IOException e) {
} Log.d(Config.LOGTAG, account.getJid().asBareJid() + " " + e.getMessage());
} callback.onFileTransferAborted();
this.remainingSize -= count; }
this.digest.update(buffer,0,count); }
String base64 = Base64.encodeToString(buffer,0,count, Base64.NO_WRAP);
IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
iq.setTo(this.counterpart);
Element data = iq.addChild("data", "http://jabber.org/protocol/ibb");
data.setAttribute("seq", Integer.toString(this.seq));
data.setAttribute("block-size", Integer.toString(this.blockSize));
data.setAttribute("sid", this.sessionId);
data.setContent(base64);
this.account.getXmppConnection().sendIqPacket(iq, this.onAckReceived);
this.account.getXmppConnection().r(); //don't fill up stanza queue too much
this.seq++;
connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
if (this.remainingSize <= 0) {
sendClose();
file.setSha1Sum(digest.digest());
this.onFileTransmissionStatusChanged.onFileTransmitted(file);
fileInputStream.close();
}
} catch (IOException e) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": io exception during sendNextBlock() "+e.getMessage());
FileBackend.close(fileInputStream);
this.onFileTransmissionStatusChanged.onFileTransferAborted();
}
}
private void receiveNextBlock(String data) { @Override
try { public void send(DownloadableFile file, OnFileTransmissionStatusChanged callback) {
byte[] buffer = Base64.decode(data, Base64.NO_WRAP); this.onFileTransmissionStatusChanged = callback;
if (this.remainingSize < buffer.length) { this.file = file;
buffer = Arrays.copyOfRange(buffer, 0, (int) this.remainingSize); try {
} this.remainingSize = this.file.getExpectedSize();
this.remainingSize -= buffer.length; this.fileSize = this.remainingSize;
this.fileOutputStream.write(buffer); this.digest = MessageDigest.getInstance("SHA-1");
this.digest.update(buffer); this.digest.reset();
if (this.remainingSize <= 0) { fileInputStream = connection.getFileInputStream();
file.setSha1Sum(digest.digest()); if (fileInputStream == null) {
fileOutputStream.flush(); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": could no create input stream");
fileOutputStream.close(); callback.onFileTransferAborted();
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": receive next block nothing remaining"); return;
this.onFileTransmissionStatusChanged.onFileTransmitted(file); }
} else { innerInputStream = AbstractConnectionManager.upgrade(file, fileInputStream);
connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100)); if (this.connected) {
} this.sendNextBlock();
} catch (Exception e) { }
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": "+e.getMessage()); } catch (Exception e) {
FileBackend.close(fileOutputStream); callback.onFileTransferAborted();
this.onFileTransmissionStatusChanged.onFileTransferAborted(); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": " + e.getMessage());
} }
} }
public void deliverPayload(IqPacket packet, Element payload) { @Override
if (payload.getName().equals("open")) { public void disconnect() {
if (!established) { this.connected = false;
established = true; FileBackend.close(fileOutputStream);
connected = true; FileBackend.close(fileInputStream);
this.receiveNextBlock(""); }
this.account.getXmppConnection().sendIqPacket(
packet.generateResponse(IqPacket.TYPE.RESULT), null); private void sendNextBlock() {
} else { byte[] buffer = new byte[this.blockSize];
this.account.getXmppConnection().sendIqPacket( try {
packet.generateResponse(IqPacket.TYPE.ERROR), null); int count = innerInputStream.read(buffer);
} if (count == -1) {
} else if (connected && payload.getName().equals("data")) { sendClose();
this.receiveNextBlock(payload.getContent()); file.setSha1Sum(digest.digest());
this.account.getXmppConnection().sendIqPacket( Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": sendNextBlock() count was -1");
packet.generateResponse(IqPacket.TYPE.RESULT), null); this.onFileTransmissionStatusChanged.onFileTransmitted(file);
} else if (connected && payload.getName().equals("close")) { fileInputStream.close();
this.connected = false; return;
this.account.getXmppConnection().sendIqPacket( } else if (count != buffer.length) {
packet.generateResponse(IqPacket.TYPE.RESULT), null); int rem = innerInputStream.read(buffer, count, buffer.length - count);
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": received ibb close"); if (rem > 0) {
} else { count += rem;
Log.d(Config.LOGTAG,payload.toString()); }
// TODO some sort of exception }
} this.remainingSize -= count;
} this.digest.update(buffer, 0, count);
String base64 = Base64.encodeToString(buffer, 0, count, Base64.NO_WRAP);
IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
iq.setTo(this.counterpart);
Element data = iq.addChild("data", "http://jabber.org/protocol/ibb");
data.setAttribute("seq", Integer.toString(this.seq));
data.setAttribute("block-size", Integer.toString(this.blockSize));
data.setAttribute("sid", this.sessionId);
data.setContent(base64);
this.account.getXmppConnection().sendIqPacket(iq, this.onAckReceived);
this.account.getXmppConnection().r(); //don't fill up stanza queue too much
this.seq++;
connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
if (this.remainingSize <= 0) {
sendClose();
file.setSha1Sum(digest.digest());
this.onFileTransmissionStatusChanged.onFileTransmitted(file);
fileInputStream.close();
}
} catch (IOException e) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": io exception during sendNextBlock() " + e.getMessage());
FileBackend.close(fileInputStream);
this.onFileTransmissionStatusChanged.onFileTransferAborted();
}
}
private void receiveNextBlock(String data) {
try {
byte[] buffer = Base64.decode(data, Base64.NO_WRAP);
if (this.remainingSize < buffer.length) {
buffer = Arrays.copyOfRange(buffer, 0, (int) this.remainingSize);
}
this.remainingSize -= buffer.length;
this.fileOutputStream.write(buffer);
this.digest.update(buffer);
if (this.remainingSize <= 0) {
file.setSha1Sum(digest.digest());
fileOutputStream.flush();
fileOutputStream.close();
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": receive next block nothing remaining");
this.onFileTransmissionStatusChanged.onFileTransmitted(file);
} else {
connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
}
} catch (Exception e) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": " + e.getMessage());
FileBackend.close(fileOutputStream);
this.onFileTransmissionStatusChanged.onFileTransferAborted();
}
}
public void deliverPayload(IqPacket packet, Element payload) {
if (payload.getName().equals("open")) {
if (!established) {
established = true;
connected = true;
this.receiveNextBlock("");
this.account.getXmppConnection().sendIqPacket(
packet.generateResponse(IqPacket.TYPE.RESULT), null);
} else {
this.account.getXmppConnection().sendIqPacket(
packet.generateResponse(IqPacket.TYPE.ERROR), null);
}
} else if (connected && payload.getName().equals("data")) {
this.receiveNextBlock(payload.getContent());
this.account.getXmppConnection().sendIqPacket(
packet.generateResponse(IqPacket.TYPE.RESULT), null);
} else if (connected && payload.getName().equals("close")) {
this.connected = false;
this.account.getXmppConnection().sendIqPacket(
packet.generateResponse(IqPacket.TYPE.RESULT), null);
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received ibb close");
} else {
Log.d(Config.LOGTAG, payload.toString());
// TODO some sort of exception
}
}
} }

View File

@ -26,6 +26,10 @@ import eu.siacs.conversations.utils.WakeLockHelper;
import eu.siacs.conversations.xmpp.jingle.stanzas.Content; import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
public class JingleSocks5Transport extends JingleTransport { public class JingleSocks5Transport extends JingleTransport {
private static final int SOCKET_TIMEOUT_DIRECT = 3000;
private static final int SOCKET_TIMEOUT_PROXY = 5000;
private final JingleCandidate candidate; private final JingleCandidate candidate;
private final JingleConnection connection; private final JingleConnection connection;
private final String destination; private final String destination;
@ -92,8 +96,9 @@ public class JingleSocks5Transport extends JingleTransport {
} }
} }
private void acceptIncomingSocketConnection(Socket socket) throws IOException { private void acceptIncomingSocketConnection(final Socket socket) throws IOException {
Log.d(Config.LOGTAG, "accepted connection from " + socket.getInetAddress().getHostAddress()); Log.d(Config.LOGTAG, "accepted connection from " + socket.getInetAddress().getHostAddress());
socket.setSoTimeout(SOCKET_TIMEOUT_DIRECT);
final byte[] authBegin = new byte[2]; final byte[] authBegin = new byte[2];
final InputStream inputStream = socket.getInputStream(); final InputStream inputStream = socket.getInputStream();
final OutputStream outputStream = socket.getOutputStream(); final OutputStream outputStream = socket.getOutputStream();
@ -115,7 +120,8 @@ public class JingleSocks5Transport extends JingleTransport {
int destinationCount = inputStream.read(); int destinationCount = inputStream.read();
final byte[] destination = new byte[destinationCount]; final byte[] destination = new byte[destinationCount];
inputStream.read(destination); inputStream.read(destination);
final int port = inputStream.read(); final byte[] port = new byte[2];
inputStream.read(port);
final String receivedDestination = new String(destination); final String receivedDestination = new String(destination);
final ByteBuffer response = ByteBuffer.allocate(7 + destination.length); final ByteBuffer response = ByteBuffer.allocate(7 + destination.length);
final byte[] responseHeader; final byte[] responseHeader;
@ -131,11 +137,12 @@ public class JingleSocks5Transport extends JingleTransport {
response.put(responseHeader); response.put(responseHeader);
response.put((byte) destination.length); response.put((byte) destination.length);
response.put(destination); response.put(destination);
response.putShort((short) port); response.put(port);
outputStream.write(response.array()); outputStream.write(response.array());
outputStream.flush(); outputStream.flush();
if (success) { if (success) {
Log.d(Config.LOGTAG,connection.getAccount().getJid().asBareJid()+": successfully processed connection to candidate "+candidate.getHost()+":"+candidate.getPort()); Log.d(Config.LOGTAG,connection.getAccount().getJid().asBareJid()+": successfully processed connection to candidate "+candidate.getHost()+":"+candidate.getPort());
socket.setSoTimeout(0);
this.socket = socket; this.socket = socket;
this.inputStream = inputStream; this.inputStream = inputStream;
this.outputStream = outputStream; this.outputStream = outputStream;
@ -151,6 +158,7 @@ public class JingleSocks5Transport extends JingleTransport {
public void connect(final OnTransportConnected callback) { public void connect(final OnTransportConnected callback) {
new Thread(() -> { new Thread(() -> {
final int timeout = candidate.getType() == JingleCandidate.TYPE_DIRECT ? SOCKET_TIMEOUT_DIRECT : SOCKET_TIMEOUT_PROXY;
try { try {
final boolean useTor = connection.getAccount().isOnion() || connection.getConnectionManager().getXmppConnectionService().useTorToConnect(); final boolean useTor = connection.getAccount().isOnion() || connection.getConnectionManager().getXmppConnectionService().useTorToConnect();
if (useTor) { if (useTor) {
@ -158,11 +166,11 @@ public class JingleSocks5Transport extends JingleTransport {
} else { } else {
socket = new Socket(); socket = new Socket();
SocketAddress address = new InetSocketAddress(candidate.getHost(), candidate.getPort()); SocketAddress address = new InetSocketAddress(candidate.getHost(), candidate.getPort());
socket.connect(address, 5000); socket.connect(address, timeout);
} }
inputStream = socket.getInputStream(); inputStream = socket.getInputStream();
outputStream = socket.getOutputStream(); outputStream = socket.getOutputStream();
socket.setSoTimeout(5000); socket.setSoTimeout(timeout);
SocksSocketFactory.createSocksConnection(socket, destination, 0); SocksSocketFactory.createSocksConnection(socket, destination, 0);
socket.setSoTimeout(0); socket.setSoTimeout(0);
isEstablished = true; isEstablished = true;

View File

@ -14,14 +14,27 @@ public class PublishOptions {
public static Bundle openAccess() { public static Bundle openAccess() {
final Bundle options = new Bundle(); final Bundle options = new Bundle();
options.putString("pubsub#access_model","open"); options.putString("pubsub#access_model", "open");
return options; return options;
} }
public static Bundle persistentWhitelistAccess() { public static Bundle persistentWhitelistAccess() {
final Bundle options = new Bundle(); final Bundle options = new Bundle();
options.putString("pubsub#persist_items","true"); options.putString("pubsub#persist_items", "true");
options.putString("pubsub#access_model","whitelist"); options.putString("pubsub#access_model", "whitelist");
return options;
}
public static Bundle persistentWhitelistAccessMaxItems() {
final Bundle options = new Bundle();
options.putString("pubsub#persist_items", "true");
options.putString("pubsub#access_model", "whitelist");
options.putString("pubsub#send_last_published_item", "never");
options.putString("pubsub#max_items", "128"); //YOLO!
options.putString("pubsub#notify_delete", "true");
options.putString("pubsub#notify_retract", "true"); //one could also set notify=true on the retract
return options; return options;
} }

View File

@ -337,6 +337,7 @@
<string name="x_file_offered_for_download">%s zum Herunterladen angeboten</string> <string name="x_file_offered_for_download">%s zum Herunterladen angeboten</string>
<string name="cancel_transmission">Übertragung abbrechen</string> <string name="cancel_transmission">Übertragung abbrechen</string>
<string name="file_transmission_failed">Übertragung fehlgeschlagen</string> <string name="file_transmission_failed">Übertragung fehlgeschlagen</string>
<string name="file_transmission_cancelled">Übertragung abgebrochen</string>
<string name="file_deleted">Datei wurde gelöscht</string> <string name="file_deleted">Datei wurde gelöscht</string>
<string name="no_application_found_to_open_file">Keine Anwendung zum Öffnen der Datei gefunden</string> <string name="no_application_found_to_open_file">Keine Anwendung zum Öffnen der Datei gefunden</string>
<string name="no_application_found_to_open_link">Keine Anwendung zum Öffnen des Links gefunden</string> <string name="no_application_found_to_open_link">Keine Anwendung zum Öffnen des Links gefunden</string>
@ -857,7 +858,7 @@
<string name="discover_channels">Channels entdecken</string> <string name="discover_channels">Channels entdecken</string>
<string name="search_channels">Channels suchen</string> <string name="search_channels">Channels suchen</string>
<string name="channel_discovery_opt_in_title">Mögliche Datenschutzverletzung!</string> <string name="channel_discovery_opt_in_title">Mögliche Datenschutzverletzung!</string>
<string name="channel_discover_opt_in_message"><![CDATA[Die Channel-Entdeckung verwendet einen Drittanbieterservice namens <a href="https://search.jabbercat.org">search.jabbercat.org</a>.<br><br>Wenn du diese Funktion verwendest, werden deine IP-Adresse und deine Suchbegriffe an diesen Dienst übertragen. Weitere Informationen findest du in der <a href="https://search.jabbercat.org/privacy">Datenschutzerklärung</a>.]]></string> <string name="channel_discover_opt_in_message"><![CDATA[Die Channel-Entdeckung verwendet einen Drittanbieterservice namens <a href="https://search.jabber.network">search.jabber.network</a>.<br><br>Wenn du diese Funktion verwendest, werden deine IP-Adresse und deine Suchbegriffe an diesen Dienst übertragen. Weitere Informationen findest du in der <a href="https://search.jabber.network/privacy">Datenschutzerklärung</a>.]]></string>
<string name="i_already_have_an_account">Ich habe bereits ein Konto</string> <string name="i_already_have_an_account">Ich habe bereits ein Konto</string>
<string name="add_existing_account">Vorhandenes Konto hinzufügen</string> <string name="add_existing_account">Vorhandenes Konto hinzufügen</string>
<string name="register_new_account">Neues Konto erstellen</string> <string name="register_new_account">Neues Konto erstellen</string>

View File

@ -857,7 +857,6 @@
<string name="discover_channels">Ανακάλυψη καναλιών</string> <string name="discover_channels">Ανακάλυψη καναλιών</string>
<string name="search_channels">Αναζήτηση καναλιών</string> <string name="search_channels">Αναζήτηση καναλιών</string>
<string name="channel_discovery_opt_in_title">Πιθανή παραβίαση ιδιωτικότητας!</string> <string name="channel_discovery_opt_in_title">Πιθανή παραβίαση ιδιωτικότητας!</string>
<string name="channel_discover_opt_in_message"><![CDATA[Η ανακάλυψη καναλιού χρησιμοποιεί μια τρίτη υπηρεσία που λέγεται <a href="https://search.jabbercat.org">search.jabbercat.org</a>.<br><br>Χρησιμοποιώντας αυτή τη λειτουργία θα μεταβιβαστεί η διεύθυνση IP σας και οι όροι αναζήτησης σε αυτή την υπηρεσία. Δείτε την <a href="https://search.jabbercat.org/privacy">Πολιτική Ιδιωτικότητας</a> της για περισσότερες πληροφορίες.]]></string>
<string name="i_already_have_an_account">Έχω ήδη λογαριασμό</string> <string name="i_already_have_an_account">Έχω ήδη λογαριασμό</string>
<string name="add_existing_account">Προσθήκη υπάρχοντος λογαριασμού</string> <string name="add_existing_account">Προσθήκη υπάρχοντος λογαριασμού</string>
<string name="register_new_account">Εγγραφή νέου λογαριασμού</string> <string name="register_new_account">Εγγραφή νέου λογαριασμού</string>

View File

@ -857,7 +857,6 @@
<string name="discover_channels">Descubrir canales</string> <string name="discover_channels">Descubrir canales</string>
<string name="search_channels">Buscar canales</string> <string name="search_channels">Buscar canales</string>
<string name="channel_discovery_opt_in_title">¡Posible violación de privacidad!</string> <string name="channel_discovery_opt_in_title">¡Posible violación de privacidad!</string>
<string name="channel_discover_opt_in_message"><![CDATA[La búsqueda de canales usa un servicio de terceros llamado search.jabbercat.org<a href="https://search.jabbercat.org">. <br><br>Usando esta funcionalidad transmitirás tu dirección IP y los términos buscados a este servicio. Ver su <a href="https://search.jabbercat.org/privacy">Política de Privacidad</a> para más información.]]></string>
<string name="i_already_have_an_account">Ya tengo una cuenta</string> <string name="i_already_have_an_account">Ya tengo una cuenta</string>
<string name="add_existing_account">Añadir una cuenta existente</string> <string name="add_existing_account">Añadir una cuenta existente</string>
<string name="register_new_account">Registrar una cuenta nueva</string> <string name="register_new_account">Registrar una cuenta nueva</string>
@ -872,4 +871,5 @@
<string name="account_already_setup">Esta cuenta ya fue configurada</string> <string name="account_already_setup">Esta cuenta ya fue configurada</string>
<string name="please_enter_password">Por favor ingrese la contraseña para esta cuenta</string> <string name="please_enter_password">Por favor ingrese la contraseña para esta cuenta</string>
<string name="unable_to_perform_this_action">No se ha podido realizar esta acción</string> <string name="unable_to_perform_this_action">No se ha podido realizar esta acción</string>
<string name="open_join_dialog">Unirse a canal público...</string>
</resources> </resources>

View File

@ -30,6 +30,7 @@
<string name="just_now">orain</string> <string name="just_now">orain</string>
<string name="minute_ago">min 1 lehenago</string> <string name="minute_ago">min 1 lehenago</string>
<string name="minutes_ago">%d min lehenago</string> <string name="minutes_ago">%d min lehenago</string>
<string name="x_unread_conversations">Irakurrik gabeko %d elkarrizketa</string>
<string name="sending">bidaltzen…</string> <string name="sending">bidaltzen…</string>
<string name="message_decrypting">Mezua desenkriptatzen. Mesedez itxaron…</string> <string name="message_decrypting">Mezua desenkriptatzen. Mesedez itxaron…</string>
<string name="pgp_message">OpenPGPz enkriptatutako mezua</string> <string name="pgp_message">OpenPGPz enkriptatutako mezua</string>
@ -336,6 +337,7 @@
<string name="x_file_offered_for_download">%s deskargatzeko eskeinita</string> <string name="x_file_offered_for_download">%s deskargatzeko eskeinita</string>
<string name="cancel_transmission">Transmisioa utzi</string> <string name="cancel_transmission">Transmisioa utzi</string>
<string name="file_transmission_failed">fitxategi transmisioak huts egin du</string> <string name="file_transmission_failed">fitxategi transmisioak huts egin du</string>
<string name="file_transmission_cancelled">fitxategiaren transmisioa utzi egin da</string>
<string name="file_deleted">Fitxategia ezabatu egin da</string> <string name="file_deleted">Fitxategia ezabatu egin da</string>
<string name="no_application_found_to_open_file">Fitxategia ireki dezakeen aplikaziorik ez da aurkitu</string> <string name="no_application_found_to_open_file">Fitxategia ireki dezakeen aplikaziorik ez da aurkitu</string>
<string name="no_application_found_to_open_link">Ez da lotura hau ireki dezakeen aplikaziorik aurkitu</string> <string name="no_application_found_to_open_link">Ez da lotura hau ireki dezakeen aplikaziorik aurkitu</string>
@ -854,7 +856,7 @@
<string name="discover_channels">Kanalak aurkitu</string> <string name="discover_channels">Kanalak aurkitu</string>
<string name="search_channels">Kanalak bilatu</string> <string name="search_channels">Kanalak bilatu</string>
<string name="channel_discovery_opt_in_title">Balizko pribatutasun urraketa!</string> <string name="channel_discovery_opt_in_title">Balizko pribatutasun urraketa!</string>
<string name="channel_discover_opt_in_message"><![CDATA[Kanalak aurkitzeko ezaugarriak <a href="https://search.jabbercat.org">search.jabbercat.org</a> izeneko hirugarren zerbitzu bat erabiltzen du.<br><br>Ezaugarri hau erabiltzeak zure IP helbidea eta bilatutako testua zerbitzu horretara bidaltzea dakar. Ikusi beren <a href="https://search.jabbercat.org/privacy">pribatutasun politika</a> informazio gehiago lortzeko.]]></string> <string name="channel_discover_opt_in_message"><![CDATA[Kanalak aurkitzeko ezaugarriak <a href="https://search.jabber.network">search.jabber.network</a>. izeneko hirugarren zerbitzu bat erabiltzen du.<br><br>Ezaugarri hau erabiltzeak zure IP helbidea eta bilatutako testua zerbitzu horretara bidaltzea dakar. Ikusi beren <a href="https://search.jabber.network/privacy">pribatutasun politika</a> informazio gehiago lortzeko.]]></string>
<string name="i_already_have_an_account">Badaukat kontu bat dagoeneko</string> <string name="i_already_have_an_account">Badaukat kontu bat dagoeneko</string>
<string name="add_existing_account">Gehitu existitzen den kontu bat</string> <string name="add_existing_account">Gehitu existitzen den kontu bat</string>
<string name="register_new_account">Kontu berria erregistratu</string> <string name="register_new_account">Kontu berria erregistratu</string>
@ -868,4 +870,6 @@
<string name="not_a_backup_file">Hautatu duzun fitxategia ez da Conversations babes-kopia bat</string> <string name="not_a_backup_file">Hautatu duzun fitxategia ez da Conversations babes-kopia bat</string>
<string name="account_already_setup">Kontu hau konfiguratuta dago jada</string> <string name="account_already_setup">Kontu hau konfiguratuta dago jada</string>
<string name="please_enter_password">Mesedez idatzi ezazu kontu honetarako pasahitza</string> <string name="please_enter_password">Mesedez idatzi ezazu kontu honetarako pasahitza</string>
<string name="unable_to_perform_this_action">Ezin izan da ekintza hau burutu</string>
<string name="open_join_dialog">Kanal publiko batean sartu…</string>
</resources> </resources>

View File

@ -7,7 +7,7 @@
<string name="action_end_conversation">Fermer cette conversation</string> <string name="action_end_conversation">Fermer cette conversation</string>
<string name="action_contact_details">Détails du contact</string> <string name="action_contact_details">Détails du contact</string>
<string name="action_muc_details">Détails de la conversation de groupe</string> <string name="action_muc_details">Détails de la conversation de groupe</string>
<string name="channel_details">Détails de la chaîne</string> <string name="channel_details">Détails du canal</string>
<string name="action_secure">Conversation sécurisée</string> <string name="action_secure">Conversation sécurisée</string>
<string name="action_add_account">Ajouter un compte</string> <string name="action_add_account">Ajouter un compte</string>
<string name="action_edit_contact">Modifier le nom</string> <string name="action_edit_contact">Modifier le nom</string>
@ -17,6 +17,8 @@
<string name="action_unblock_contact">Débloquer le contact</string> <string name="action_unblock_contact">Débloquer le contact</string>
<string name="action_block_domain">Bloquer le domaine</string> <string name="action_block_domain">Bloquer le domaine</string>
<string name="action_unblock_domain">Débloquer le domaine</string> <string name="action_unblock_domain">Débloquer le domaine</string>
<string name="action_block_participant">Bloquer le participant</string>
<string name="action_unblock_participant">Débloquer le participant</string>
<string name="title_activity_manage_accounts">Gestion des comptes</string> <string name="title_activity_manage_accounts">Gestion des comptes</string>
<string name="title_activity_settings">Paramètres</string> <string name="title_activity_settings">Paramètres</string>
<string name="title_activity_sharewith">Partager avec Conversation</string> <string name="title_activity_sharewith">Partager avec Conversation</string>
@ -28,6 +30,7 @@
<string name="just_now">À l\'instant</string> <string name="just_now">À l\'instant</string>
<string name="minute_ago">Il y a 1 minute</string> <string name="minute_ago">Il y a 1 minute</string>
<string name="minutes_ago">Il y a %d minutes</string> <string name="minutes_ago">Il y a %d minutes</string>
<string name="x_unread_conversations">%d conversations non lues</string>
<string name="sending">Envoi…</string> <string name="sending">Envoi…</string>
<string name="message_decrypting">Déchiffrement du message. Veuillez patienter...</string> <string name="message_decrypting">Déchiffrement du message. Veuillez patienter...</string>
<string name="pgp_message">Message chiffré avec OpenPGP</string> <string name="pgp_message">Message chiffré avec OpenPGP</string>
@ -334,6 +337,7 @@
<string name="x_file_offered_for_download">%s proposé à télécharger</string> <string name="x_file_offered_for_download">%s proposé à télécharger</string>
<string name="cancel_transmission">Annuler l\'envoi</string> <string name="cancel_transmission">Annuler l\'envoi</string>
<string name="file_transmission_failed">Échec de l\'envoi du fichier</string> <string name="file_transmission_failed">Échec de l\'envoi du fichier</string>
<string name="file_transmission_cancelled">Transfert de fichier annulé</string>
<string name="file_deleted">Le fichier a été supprimé</string> <string name="file_deleted">Le fichier a été supprimé</string>
<string name="no_application_found_to_open_file">Aucune application disponible pour ouvrir le fichier</string> <string name="no_application_found_to_open_file">Aucune application disponible pour ouvrir le fichier</string>
<string name="no_application_found_to_open_link">Aucune application trouvée pour ouvrir le lien</string> <string name="no_application_found_to_open_link">Aucune application trouvée pour ouvrir le lien</string>
@ -701,7 +705,7 @@
<string name="small">Petite</string> <string name="small">Petite</string>
<string name="medium">Moyenne</string> <string name="medium">Moyenne</string>
<string name="large">Grande</string> <string name="large">Grande</string>
<string name="not_encrypted_for_this_device">OMEMO sera utilisé par défaut pour toute nouvelle conversation.</string> <string name="not_encrypted_for_this_device">Le message n\'était pas chiffré pour cet appareil.</string>
<string name="omemo_decryption_failed">Échec de déchiffrement du message OMEMO.</string> <string name="omemo_decryption_failed">Échec de déchiffrement du message OMEMO.</string>
<string name="undo">annuler</string> <string name="undo">annuler</string>
<string name="location_disabled">Le partage de positionnement est désactivé.</string> <string name="location_disabled">Le partage de positionnement est désactivé.</string>
@ -845,7 +849,7 @@
<string name="anyone_can_invite_others">N\'importe qui peut inviter d\'autres personnes.</string> <string name="anyone_can_invite_others">N\'importe qui peut inviter d\'autres personnes.</string>
<string name="jabber_ids_are_visible_to_admins">Les adresses XMPP sont visibles par les administrateurs.</string> <string name="jabber_ids_are_visible_to_admins">Les adresses XMPP sont visibles par les administrateurs.</string>
<string name="jabber_ids_are_visible_to_anyone">Les adresses XMPP sont visibles par tous.</string> <string name="jabber_ids_are_visible_to_anyone">Les adresses XMPP sont visibles par tous.</string>
<string name="no_users_hint_channel">Cette chaîne publique n\'a pas de participants. Invitez vos contacts ou utilisez le bouton de partage pour distribuer son adresse XMPP.</string> <string name="no_users_hint_channel">Ce canal publique n\'a pas de participants. Invitez vos contacts ou utilisez le bouton de partage pour distribuer son adresse XMPP.</string>
<string name="no_users_hint_group_chat">Ce chat de groupe privé n\'a aucun participant.</string> <string name="no_users_hint_group_chat">Ce chat de groupe privé n\'a aucun participant.</string>
<string name="manage_permission">Gérer les privilèges</string> <string name="manage_permission">Gérer les privilèges</string>
<string name="search_participants">Rechercher des participants</string> <string name="search_participants">Rechercher des participants</string>
@ -854,11 +858,20 @@
<string name="discover_channels">Découverte des canaux</string> <string name="discover_channels">Découverte des canaux</string>
<string name="search_channels">Recherche des canaux</string> <string name="search_channels">Recherche des canaux</string>
<string name="channel_discovery_opt_in_title">Violation possible de la confidentialité !</string> <string name="channel_discovery_opt_in_title">Violation possible de la confidentialité !</string>
<string name="channel_discover_opt_in_message"><![CDATA[La découverte de chaînes utilise un service tiers appelé <a href="https://search.jabbercat.org">search.jabbercat.org</a> qui transmet votre adresse IP et les termes de recherche à ce service. Voir leur <a href="https://search.jabbercat.org/privacy">politique de confidentialité</a> pour plus d\'informations]]>.</string> <string name="channel_discover_opt_in_message"><![CDATA[Channel discovery utilise un service tiers appelé <a href="https://search.jabber.network">search.jabber.network</a>.<br><br>L\'utilisation de cette fonction transmettra votre adresse IP et les termes de recherche à ce service. Veuillez consulter leur <a href="https://search.jabber.network/privacy">Politique de confidentialité</a> pour plus d\'information.]]></string>
<string name="i_already_have_an_account">J\'ai déjà un compte</string> <string name="i_already_have_an_account">J\'ai déjà un compte</string>
<string name="add_existing_account">Ajouter un compte existant</string> <string name="add_existing_account">Ajouter un compte existant</string>
<string name="register_new_account">Enregistrer un nouveau compte</string> <string name="register_new_account">Enregistrer un nouveau compte</string>
<string name="this_looks_like_a_domain">Ceci ressemble à une adresse de domaine</string> <string name="this_looks_like_a_domain">Ceci ressemble à une adresse de domaine</string>
<string name="add_anway">Ajouter quand même</string> <string name="add_anway">Ajouter quand même</string>
<string name="this_looks_like_channel">Ceci ressemble à une adresse de canal</string> <string name="this_looks_like_channel">Ceci ressemble à une adresse de canal</string>
<string name="share_backup_files">Partager les fichiers de sauvegardes</string>
<string name="conversations_backup">Sauvegarder les conversations</string>
<string name="event">Événement </string>
<string name="open_backup">Ouvrir sauvegarde</string>
<string name="not_a_backup_file">Le fichier sélectionné n\'est pas une sauvegarde de Conversations</string>
<string name="account_already_setup">Ce compte a déjà été configuré</string>
<string name="please_enter_password">Veuillez saisir le mot de passe pour ce compte</string>
<string name="unable_to_perform_this_action">Action impossible à réaliser</string>
<string name="open_join_dialog">Rejoindre le canal public ...</string>
</resources> </resources>

View File

@ -857,7 +857,6 @@
<string name="discover_channels">Descubrir canales</string> <string name="discover_channels">Descubrir canales</string>
<string name="search_channels">Buscar canales</string> <string name="search_channels">Buscar canales</string>
<string name="channel_discovery_opt_in_title">Posible intrusión na intimidade!</string> <string name="channel_discovery_opt_in_title">Posible intrusión na intimidade!</string>
<string name="channel_discover_opt_in_message"><![CDATA[O descubrimento de canales utiliza un servizo de terceiros chamado <a href="https://search.jabbercat.org">search.jabbercat.org</a>.<br><br>Ao utilizar esta característica transmitirá o seu enderezo IP e os termos de busca a ese servizo. Lea a súa <a href="https://search.jabbercat.org/privacy">Política de Intimidade</a> para máis información.]]></string>
<string name="i_already_have_an_account">Xa teño unha conta</string> <string name="i_already_have_an_account">Xa teño unha conta</string>
<string name="add_existing_account">Engadir conta existente</string> <string name="add_existing_account">Engadir conta existente</string>
<string name="register_new_account">Rexistrar unha nova conta</string> <string name="register_new_account">Rexistrar unha nova conta</string>

View File

@ -856,7 +856,6 @@
<string name="discover_channels">Csatornák felderítése</string> <string name="discover_channels">Csatornák felderítése</string>
<string name="search_channels">Csatornák keresése</string> <string name="search_channels">Csatornák keresése</string>
<string name="channel_discovery_opt_in_title">Magánélet lehetséges megsértése!</string> <string name="channel_discovery_opt_in_title">Magánélet lehetséges megsértése!</string>
<string name="channel_discover_opt_in_message"><![CDATA[A csatorna felderítés egy harmadik fél által biztosított szolgáltatást használ: <a href="https://search.jabbercat.org">search.jabbercat.org</a>.<br><br>Ezen funkció használata során át fog kerülni az IP címe és a keresési kifejezés ahhoz a szolgáltatáshoz. További információért tekintse meg az <a href="https://search.jabbercat.org/privacy">Adatvédelmi Irányelveiket</a>.]]></string>
<string name="i_already_have_an_account">Már rendelkezem fiókkal</string> <string name="i_already_have_an_account">Már rendelkezem fiókkal</string>
<string name="add_existing_account">Már létező fiók hozzáadása</string> <string name="add_existing_account">Már létező fiók hozzáadása</string>
<string name="register_new_account">Új fiók létrehozása</string> <string name="register_new_account">Új fiók létrehozása</string>

View File

@ -857,7 +857,6 @@
<string name="discover_channels">Individua i canali</string> <string name="discover_channels">Individua i canali</string>
<string name="search_channels">Cerca i canali</string> <string name="search_channels">Cerca i canali</string>
<string name="channel_discovery_opt_in_title">Possibile violazione della privacy!</string> <string name="channel_discovery_opt_in_title">Possibile violazione della privacy!</string>
<string name="channel_discover_opt_in_message"><![CDATA[La ricerca dei canali usa un servizio di terze parti chiamato <a href="https://search.jabbercat.org">search.jabbercat.org</a>.<br><br>Usando questa opzione trasmetterai il tuo indirizzo IP e la stringa di ricerca al servizio. Controlla la loro <a href="https://search.jabbercat.org/privacy">Policy per la privacy</a> per maggiori informazioni.]]></string>
<string name="i_already_have_an_account">Ho già un account</string> <string name="i_already_have_an_account">Ho già un account</string>
<string name="add_existing_account">Aggiungi un account pre-esistente</string> <string name="add_existing_account">Aggiungi un account pre-esistente</string>
<string name="register_new_account">Registra un nuovo account</string> <string name="register_new_account">Registra un nuovo account</string>

View File

@ -337,6 +337,7 @@
<string name="x_file_offered_for_download">%s aangeboden om te downloaden</string> <string name="x_file_offered_for_download">%s aangeboden om te downloaden</string>
<string name="cancel_transmission">Bestandsoverdracht annuleren</string> <string name="cancel_transmission">Bestandsoverdracht annuleren</string>
<string name="file_transmission_failed">bestandsoverdracht mislukt</string> <string name="file_transmission_failed">bestandsoverdracht mislukt</string>
<string name="file_transmission_cancelled">bestandsoverdracht geannuleerd</string>
<string name="file_deleted">Het bestand is verwijderd</string> <string name="file_deleted">Het bestand is verwijderd</string>
<string name="no_application_found_to_open_file">Geen applicatie om bestand te openen</string> <string name="no_application_found_to_open_file">Geen applicatie om bestand te openen</string>
<string name="no_application_found_to_open_link">Geen applicatie om verwijzing te openen</string> <string name="no_application_found_to_open_link">Geen applicatie om verwijzing te openen</string>
@ -856,7 +857,7 @@
<string name="discover_channels">Kanalen ontdekken</string> <string name="discover_channels">Kanalen ontdekken</string>
<string name="search_channels">Kanalen doorzoeken</string> <string name="search_channels">Kanalen doorzoeken</string>
<string name="channel_discovery_opt_in_title">Mogelijke privacyschending!</string> <string name="channel_discovery_opt_in_title">Mogelijke privacyschending!</string>
<string name="channel_discover_opt_in_message"><![CDATA[Kanaalontdekking maakt gebruik van een derdepartijdienst genaamd <a href="https://search.jabbercat.org">search.jabbercat.org</a>.<br><br>Door deze functie te gebruiken, zullen je IP-adres en zoekopdrachten naar die dienst verstuurd worden. Bekijk hun <a href="https://search.jabbercat.org/privacy">privacybeleid</a> voor meer informatie.]]></string> <string name="channel_discover_opt_in_message"><![CDATA[Kanaalontdekking maakt gebruik van een derdepartijdienst genaamd <a href="https://search.jabber.network">search.jabber.network</a>.<br><br>Door deze functie te gebruiken, zullen je IP-adres en zoekopdrachten naar die dienst verstuurd worden. Bekijk hun <a href="https://search.jabber.network/privacy">privacybeleid</a> voor meer informatie.]]></string>
<string name="i_already_have_an_account">Ik heb al een account</string> <string name="i_already_have_an_account">Ik heb al een account</string>
<string name="add_existing_account">Bestaande account toevoegen</string> <string name="add_existing_account">Bestaande account toevoegen</string>
<string name="register_new_account">Nieuwe account registreren</string> <string name="register_new_account">Nieuwe account registreren</string>
@ -871,4 +872,5 @@
<string name="account_already_setup">Deze account is al ingesteld</string> <string name="account_already_setup">Deze account is al ingesteld</string>
<string name="please_enter_password">Voer het wachtwoord voor deze account in</string> <string name="please_enter_password">Voer het wachtwoord voor deze account in</string>
<string name="unable_to_perform_this_action">Kan deze actie niet uitvoeren</string> <string name="unable_to_perform_this_action">Kan deze actie niet uitvoeren</string>
<string name="open_join_dialog">Deelnemen aan openbaar kanaal…</string>
</resources> </resources>

View File

@ -874,7 +874,6 @@ Administrator twojego serwera będzie mógł czytać twoje wiadomości, ale moż
<string name="discover_channels">Odkryj kanały</string> <string name="discover_channels">Odkryj kanały</string>
<string name="search_channels">Wyszukaj kanał</string> <string name="search_channels">Wyszukaj kanał</string>
<string name="channel_discovery_opt_in_title">Możliwe naruszenie prywatności!</string> <string name="channel_discovery_opt_in_title">Możliwe naruszenie prywatności!</string>
<string name="channel_discover_opt_in_message"><![CDATA[Odkrywanie kanałów używa usługi firmy trzeciej <a href="https://search.jabbercat.org">search.jabbercat.org</a>. <br><br>Używając tej funkcji twój adres IP oraz kryteria wyszukiwania zostaną wysłane do tej usługi. Sprawdź <a href="https://search.jabbercat.org/privacy">Politykę Prywatności</a> aby uzyskać więcej informacji.]]></string>
<string name="i_already_have_an_account">Już mam konto</string> <string name="i_already_have_an_account">Już mam konto</string>
<string name="add_existing_account">Dodaj istniejące konto</string> <string name="add_existing_account">Dodaj istniejące konto</string>
<string name="register_new_account">Zarejestruj nowe konto</string> <string name="register_new_account">Zarejestruj nowe konto</string>

View File

@ -856,7 +856,6 @@
<string name="discover_channels">Descobrir canais</string> <string name="discover_channels">Descobrir canais</string>
<string name="search_channels">Pesquisar canais</string> <string name="search_channels">Pesquisar canais</string>
<string name="channel_discovery_opt_in_title">Provável violação de privacidade!</string> <string name="channel_discovery_opt_in_title">Provável violação de privacidade!</string>
<string name="channel_discover_opt_in_message"><![CDATA[A descoberta de canais utiliza um serviço de terceiros chamado <a href="https://search.jabbercat.org">search.jabbercat.org</a>.<br><br>Ao usar esse recurso, você enviará o seu endereço IP e termos de pesquisa para esse serviço. Veja sua <a href="https://search.jabbercat.org/privacy">Política de Privacidade</a> para maiores informações.]]></string>
<string name="i_already_have_an_account">Eu já tenho uma conta.</string> <string name="i_already_have_an_account">Eu já tenho uma conta.</string>
<string name="add_existing_account">Adicionar uma conta já existente</string> <string name="add_existing_account">Adicionar uma conta já existente</string>
<string name="register_new_account">Registrar uma nova conta</string> <string name="register_new_account">Registrar uma nova conta</string>

View File

@ -337,6 +337,7 @@
<string name="x_file_offered_for_download">%s - fișier oferit spre descărcare</string> <string name="x_file_offered_for_download">%s - fișier oferit spre descărcare</string>
<string name="cancel_transmission">Anulează transmisiunea</string> <string name="cancel_transmission">Anulează transmisiunea</string>
<string name="file_transmission_failed">transmisie fișier eșuată</string> <string name="file_transmission_failed">transmisie fișier eșuată</string>
<string name="file_transmission_cancelled">transmisia fișierului a fost anulată</string>
<string name="file_deleted">Fișierul a fost șters</string> <string name="file_deleted">Fișierul a fost șters</string>
<string name="no_application_found_to_open_file">Nu s-a găsit nici o aplicație care să deschidă fișierul</string> <string name="no_application_found_to_open_file">Nu s-a găsit nici o aplicație care să deschidă fișierul</string>
<string name="no_application_found_to_open_link">Nu s-a găsit nici o aplicație care să deschidă legătura</string> <string name="no_application_found_to_open_link">Nu s-a găsit nici o aplicație care să deschidă legătura</string>
@ -866,7 +867,7 @@ sau chiar pierderea mesajelor.\nÎn continuare veți fi rugați să dezactivați
<string name="discover_channels">Descoperă canale publice</string> <string name="discover_channels">Descoperă canale publice</string>
<string name="search_channels">Caută canale publice</string> <string name="search_channels">Caută canale publice</string>
<string name="channel_discovery_opt_in_title">Posibilă încălcare a intimității!</string> <string name="channel_discovery_opt_in_title">Posibilă încălcare a intimității!</string>
<string name="channel_discover_opt_in_message"><![CDATA[Descoperirea de canale publice folosește un serviciu terț numit <a href="https://search.jabbercat.org">search.jabbercat.org</a>.<br><br>Folosind această funcție se va transmite adresa dumneavoastră IP și cuvintele căutate către acest serviciu. Pentru mai multe informații citiți <a href="https://search.jabbercat.org/privacy">Politica de confidențialitate</a> a serviciului.]]></string> <string name="channel_discover_opt_in_message"><![CDATA[Descoperirea de canale publice folosește un serviciu terț numit <a href="https://search.jabber.network">search.jabber.network</a>.<br><br>Folosind această funcție se va transmite adresa dumneavoastră IP și cuvintele căutate către acest serviciu. Pentru mai multe informații citiți <a href="https://search.jabber.network/privacy">Politica de confidențialitate</a> a serviciului.]]></string>
<string name="i_already_have_an_account">Eu am deja un cont</string> <string name="i_already_have_an_account">Eu am deja un cont</string>
<string name="add_existing_account">Adaugă un cont existent</string> <string name="add_existing_account">Adaugă un cont existent</string>
<string name="register_new_account">Înregistrează un cont nou</string> <string name="register_new_account">Înregistrează un cont nou</string>

View File

@ -871,7 +871,6 @@
<string name="discover_channels">Знайти канали</string> <string name="discover_channels">Знайти канали</string>
<string name="search_channels">Шукати канали</string> <string name="search_channels">Шукати канали</string>
<string name="channel_discovery_opt_in_title">Можливе порушення приватності!</string> <string name="channel_discovery_opt_in_title">Можливе порушення приватності!</string>
<string name="channel_discover_opt_in_message"><![CDATA[Пошук каналів використовує сторонній сервіс з назвою <a href="https://search.jabbercat.org">search.jabbercat.org</a>.<br><br>Використання цієї функції передає Вашу IP адресу та пошукові запити цьому сервісу. Перегляньте їхню <a href="https://search.jabbercat.org/privacy">політику конфіденційності</a>, щоб отримати більше інформації.]]></string>
<string name="i_already_have_an_account">Я вже маю обліковий запис</string> <string name="i_already_have_an_account">Я вже маю обліковий запис</string>
<string name="add_existing_account">Додати наявний обліковий запис</string> <string name="add_existing_account">Додати наявний обліковий запис</string>
<string name="register_new_account">Зареєструвати новий обліковий запис</string> <string name="register_new_account">Зареєструвати новий обліковий запис</string>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="ConversationsTheme.Dark" parent="ConversationsTheme.Dark.Base">
<item name="android:navigationBarColor">@color/black</item>
</style>
</resources>

View File

@ -850,7 +850,6 @@
<string name="discover_channels">发现群聊</string> <string name="discover_channels">发现群聊</string>
<string name="search_channels">搜索群聊</string> <string name="search_channels">搜索群聊</string>
<string name="channel_discovery_opt_in_title">可能侵犯隐私!</string> <string name="channel_discovery_opt_in_title">可能侵犯隐私!</string>
<string name="channel_discover_opt_in_message"><![CDATA[探索群聊功能使用一个叫<a href="https://search.jabbercat.org">search.jabbercat.org</a>的第三方服务。在探索群聊时您的IP地址和搜索内容将传送到他们的服务器上。有关更多信息请参阅他们的<a href="https://search.jabbercat.org/privacy">隐私政策</a>。]]></string>
<string name="i_already_have_an_account">我已有账户</string> <string name="i_already_have_an_account">我已有账户</string>
<string name="add_existing_account">添加已有账户</string> <string name="add_existing_account">添加已有账户</string>
<string name="register_new_account">注册新账户</string> <string name="register_new_account">注册新账户</string>

View File

@ -337,6 +337,7 @@
<string name="x_file_offered_for_download">%s offered for download</string> <string name="x_file_offered_for_download">%s offered for download</string>
<string name="cancel_transmission">Cancel transmission</string> <string name="cancel_transmission">Cancel transmission</string>
<string name="file_transmission_failed">file transmission failed</string> <string name="file_transmission_failed">file transmission failed</string>
<string name="file_transmission_cancelled">file transmission cancelled</string>
<string name="file_deleted">The file has been deleted</string> <string name="file_deleted">The file has been deleted</string>
<string name="no_application_found_to_open_file">No application found to open file</string> <string name="no_application_found_to_open_file">No application found to open file</string>
<string name="no_application_found_to_open_link">No application found to open link</string> <string name="no_application_found_to_open_link">No application found to open link</string>
@ -859,7 +860,7 @@
<string name="discover_channels">Discover channels</string> <string name="discover_channels">Discover channels</string>
<string name="search_channels">Search channels</string> <string name="search_channels">Search channels</string>
<string name="channel_discovery_opt_in_title">Possible privacy violation!</string> <string name="channel_discovery_opt_in_title">Possible privacy violation!</string>
<string name="channel_discover_opt_in_message"><![CDATA[Channel discovery uses a third party service called <a href="https://search.jabbercat.org">search.jabbercat.org</a>.<br><br>Using this feature will transmit your IP address and search terms to that service. See their <a href="https://search.jabbercat.org/privacy">Privacy Policy</a> for more information.]]></string> <string name="channel_discover_opt_in_message"><![CDATA[Channel discovery uses a third party service called <a href="https://search.jabber.network">search.jabber.network</a>.<br><br>Using this feature will transmit your IP address and search terms to that service. See their <a href="https://search.jabber.network/privacy">Privacy Policy</a> for more information.]]></string>
<string name="i_already_have_an_account">I already have an account</string> <string name="i_already_have_an_account">I already have an account</string>
<string name="add_existing_account">Add existing account</string> <string name="add_existing_account">Add existing account</string>
<string name="register_new_account">Register new account</string> <string name="register_new_account">Register new account</string>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources xmlns:tools="http://schemas.android.com/tools">
<style name="ConversationsTheme" parent="Theme.AppCompat.Light.NoActionBar"> <style name="ConversationsTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">@color/orange600</item> <item name="colorPrimary">@color/orange600</item>
@ -114,13 +114,12 @@
<item type="reference" name="icon_enable_undecided_device">@drawable/ic_new_releases_black_24dp</item> <item type="reference" name="icon_enable_undecided_device">@drawable/ic_new_releases_black_24dp</item>
</style> </style>
<style name="ConversationsTheme.Dark" parent="ConversationsTheme.Dark.Base" /> <style name="ConversationsTheme.Dark" parent="Theme.AppCompat.NoActionBar">
<style name="ConversationsTheme.Dark.Base" parent="Theme.AppCompat.NoActionBar">
<item name="colorPrimary">@color/orange800</item> <item name="colorPrimary">@color/orange800</item>
<item name="colorPrimaryDark">@color/orange900</item> <item name="colorPrimaryDark">@color/orange900</item>
<item name="colorAccent">@color/blue_a100</item> <item name="colorAccent">@color/blue_a100</item>
<item name="popupOverlayStyle">@style/ThemeOverlay.AppCompat.Dark</item> <item name="popupOverlayStyle">@style/ThemeOverlay.AppCompat.Dark</item>
<item name="android:navigationBarColor" tools:targetApi="21">@color/black</item>
<item name="color_background_primary">@color/black</item> <item name="color_background_primary">@color/black</item>
<item name="color_background_secondary">@color/black</item> <item name="color_background_secondary">@color/black</item>
@ -281,6 +280,7 @@
<item name="android:windowFullscreen">true</item> <item name="android:windowFullscreen">true</item>
<item name="android:windowContentOverlay">@null</item> <item name="android:windowContentOverlay">@null</item>
<item name="android:windowBackground">@android:color/black</item> <item name="android:windowBackground">@android:color/black</item>
<item name="android:navigationBarColor" tools:targetApi="21">@color/black</item>
</style> </style>
<style name="SplashTheme" parent="Theme.AppCompat.NoActionBar"> <style name="SplashTheme" parent="Theme.AppCompat.NoActionBar">

View File

@ -19,4 +19,8 @@
<string name="no_microphone_permission">Quicksy doit avoir accès au microphone</string> <string name="no_microphone_permission">Quicksy doit avoir accès au microphone</string>
<string name="foreground_service_channel_description">Cette catégorie de notification est utilisée pour afficher une notification permanente indiquant que Quicksy est en cours d\'exécution.</string> <string name="foreground_service_channel_description">Cette catégorie de notification est utilisée pour afficher une notification permanente indiquant que Quicksy est en cours d\'exécution.</string>
<string name="set_profile_picture">Photo de profil Quicksy</string> <string name="set_profile_picture">Photo de profil Quicksy</string>
</resources> <string name="not_available_in_your_country">Quicksy n\'est pas disponible dans votre pays.</string>
<string name="unable_to_verify_server_identity">Vérification de l\'identité du serveur impossible.</string>
<string name="unknown_security_error">Erreur de sécurité inconnue.</string>
<string name="timeout_while_connecting_to_server">Timeout lors de la connexion au serveur.</string>
</resources>