Merge tag '2.5.4' into develop

This commit is contained in:
Martin/Geno 2019-07-15 18:55:41 +02:00
commit 415a105b41
No known key found for this signature in database
GPG Key ID: 9D7D3C6BFF600C6A
56 changed files with 1077 additions and 563 deletions

View File

@ -1,5 +1,8 @@
# Changelog
### Version 2.5.4
* stability improvements for group chats and channels
### Version 2.5.3
* bug fixes for peer to peer file transfer (Jingle)
* fixed server info for unlimited/unknown max file size

View File

@ -51,7 +51,7 @@ dependencies {
conversationsFreeCompatImplementation "com.android.support:support-emoji-bundled:$supportLibVersion"
quicksyFreeCompatImplementation "com.android.support:support-emoji-bundled:$supportLibVersion"
implementation 'org.bouncycastle:bcmail-jdk15on:1.58'
implementation 'com.google.zxing:core:3.3.3'
implementation 'com.google.zxing:core:3.4.0'
implementation 'de.measite.minidns:minidns-hla:0.2.4'
implementation 'me.leolin:ShortcutBadger:1.1.22@aar'
implementation 'org.whispersystems:signal-protocol-java:2.6.2'
@ -59,13 +59,13 @@ dependencies {
implementation "com.wefika:flowlayout:0.4.1"
implementation 'net.ypresto.androidtranscoder:android-transcoder:0.3.0'
implementation project(':libs:xmpp-addr')
implementation 'org.osmdroid:osmdroid-android:6.0.3'
implementation 'org.osmdroid:osmdroid-android:6.1.0'
implementation 'org.hsluv:hsluv:0.2'
implementation 'org.conscrypt:conscrypt-android:1.3.0'
implementation 'org.conscrypt:conscrypt-android:2.1.0'
implementation 'me.drakeet.support:toastcompat:1.1.0'
implementation "com.leinardi.android:speed-dial:2.0.1"
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
implementation 'com.squareup.retrofit2:converter-gson:2.6.0'
implementation 'com.google.guava:guava:27.1-android'
quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.10.1'
}
@ -81,8 +81,8 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 25
versionCode 330
versionName "2.5.3"
versionCode 333
versionName "2.5.4"
archivesBaseName += "-$versionName"
applicationId "eu.sum7.conversations"
resValue "string", "applicationId", applicationId

31
proguard-rules.pro vendored
View File

@ -20,3 +20,34 @@
-dontwarn com.google.firebase.analytics.connector.AnalyticsConnector
-dontwarn java.lang.**
-dontwarn javax.lang.**
# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
# EnclosingMethod is required to use InnerClasses.
-keepattributes Signature, InnerClasses, EnclosingMethod
# Retrofit does reflection on method and parameter annotations.
-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations
# Retain service method parameters when optimizing.
-keepclassmembers,allowshrinking,allowobfuscation interface * {
@retrofit2.http.* <methods>;
}
# Ignore annotation used for build tooling.
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
# Ignore JSR 305 annotations for embedding nullability information.
-dontwarn javax.annotation.**
# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath.
-dontwarn kotlin.Unit
# Top-level functions that can only be used by Kotlin.
-dontwarn retrofit2.KotlinExtensions
-dontwarn retrofit2.KotlinExtensions$*
# With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy
# and replaces all potential values with null. Explicitly keeping the interfaces prevents this.
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface <1>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pick_a_server">Выберите своего XMPP-провайдера</string>
<string name="use_conversations.im">Использовать conversations.im</string>
<string name="create_new_account">Создать новый аккаунт</string>
</resources>

View File

@ -1,6 +1,7 @@
package eu.siacs.conversations.services;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
public class PushManagementService {
@ -10,7 +11,19 @@ public class PushManagementService {
this.mXmppConnectionService = service;
}
public void registerPushTokenOnServer(Account account) {
void registerPushTokenOnServer(Account account) {
//stub implementation. only affects playstore flavor
}
void registerPushTokenOnServer(Conversation conversation) {
//stub implementation. only affects playstore flavor
}
void unregisterChannel(Account account, String hash) {
//stub implementation. only affects playstore flavor
}
void disablePushOnServer(Conversation conversation) {
//stub implementation. only affects playstore flavor
}

View File

@ -2,7 +2,6 @@ package eu.siacs.conversations.crypto;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Parcelable;
import android.support.annotation.StringRes;
import android.util.Log;
@ -94,7 +93,7 @@ public class PgpEngine {
break;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
callback.userInputRequried(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), message);
callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), message);
break;
case OpenPgpApi.RESULT_CODE_ERROR:
OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
@ -133,7 +132,7 @@ public class PgpEngine {
callback.success(message);
break;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
callback.userInputRequried(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), message);
callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), message);
break;
case OpenPgpApi.RESULT_CODE_ERROR:
logError(conversation.getAccount(), result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
@ -200,7 +199,7 @@ public class PgpEngine {
callback.success(account);
return;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
callback.userInputRequried(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), account);
callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), account);
return;
case OpenPgpApi.RESULT_CODE_ERROR:
logError(account, result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
@ -249,7 +248,7 @@ public class PgpEngine {
callback.success(signatureBuilder.toString());
return;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
callback.userInputRequried(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), status);
callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), status);
return;
case OpenPgpApi.RESULT_CODE_ERROR:
OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
@ -276,7 +275,7 @@ public class PgpEngine {
callback.success(contact);
return;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
callback.userInputRequried(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), contact);
callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), contact);
return;
case OpenPgpApi.RESULT_CODE_ERROR:
logError(contact.getAccount(), result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));

View File

@ -64,8 +64,10 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
protected final JSONObject keys;
private final Roster roster = new Roster(this);
private final Collection<Jid> blocklist = new CopyOnWriteArraySet<>();
public List<Conversation> pendingConferenceJoins = new CopyOnWriteArrayList<>();
public List<Conversation> pendingConferenceLeaves = new CopyOnWriteArrayList<>();
public final Set<Conversation> pendingConferenceJoins = new HashSet<>();
public final Set<Conversation> pendingConferenceLeaves = new HashSet<>();
public final Set<Conversation> inProgressConferenceJoins = new HashSet<>();
public final Set<Conversation> inProgressConferencePings = new HashSet<>();
protected Jid jid;
protected String password;
protected int options = 0;

View File

@ -54,6 +54,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
public static final String ATTRIBUTE_MUTED_TILL = "muted_till";
public static final String ATTRIBUTE_ALWAYS_NOTIFY = "always_notify";
public static final String ATTRIBUTE_PUSH_NODE = "push_node";
public static final String ATTRIBUTE_LAST_CLEAR_HISTORY = "last_clear_history";
static final String ATTRIBUTE_MUC_PASSWORD = "muc_password";
private static final String ATTRIBUTE_NEXT_MESSAGE = "next_message";

View File

@ -17,6 +17,7 @@ import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.services.MessageArchiveService;
import eu.siacs.conversations.utils.JidHelper;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.forms.Data;
import eu.siacs.conversations.xmpp.forms.Field;
@ -113,6 +114,10 @@ public class MucOptions {
return MessageArchiveService.Version.has(getFeatures());
}
public boolean push() {
return getFeatures().contains(Namespace.PUSH);
}
public boolean updateConfiguration(ServiceDiscoveryResult serviceDiscoveryResult) {
this.serviceDiscoveryResult = serviceDiscoveryResult;
String name;

View File

@ -0,0 +1,87 @@
package eu.siacs.conversations.entities;
import android.content.Context;
import android.text.TextUtils;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import eu.siacs.conversations.utils.UIHelper;
import rocks.xmpp.addr.Jid;
public class RawBlockable implements ListItem, Blockable {
private final Account account;
private final Jid jid;
public RawBlockable(Account account, Jid jid) {
this.account = account;
this.jid = jid;
}
@Override
public boolean isBlocked() {
return true;
}
@Override
public boolean isDomainBlocked() {
throw new AssertionError("not implemented");
}
@Override
public Jid getBlockedJid() {
return this.jid;
}
@Override
public String getDisplayName() {
if (jid.isFullJid()) {
return jid.getResource();
} else {
return jid.toEscapedString();
}
}
@Override
public Jid getJid() {
return this.jid;
}
@Override
public List<Tag> getTags(Context context) {
return Collections.emptyList();
}
@Override
public boolean match(Context context, String needle) {
if (TextUtils.isEmpty(needle)) {
return true;
}
needle = needle.toLowerCase(Locale.US).trim();
String[] parts = needle.split("\\s+");
for (String part : parts) {
if (!jid.toEscapedString().contains(part)) {
return false;
}
}
return true;
}
@Override
public Account getAccount() {
return account;
}
@Override
public int getAvatarBackgroundColor() {
return UIHelper.getColorForName(jid.toEscapedString());
}
@Override
public int compareTo(ListItem o) {
return this.getDisplayName().compareToIgnoreCase(
o.getDisplayName());
}
}

View File

@ -305,7 +305,7 @@ public class IqGenerator extends AbstractGenerator {
public IqPacket generateSetBlockRequest(final Jid jid, boolean reportSpam) {
final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
final Element block = iq.addChild("block", Namespace.BLOCKING);
final Element item = block.addChild("item").setAttribute("jid", jid.asBareJid().toString());
final Element item = block.addChild("item").setAttribute("jid", jid.toEscapedString());
if (reportSpam) {
item.addChild("report", "urn:xmpp:reporting:0").addChild("spam");
}
@ -316,7 +316,7 @@ public class IqGenerator extends AbstractGenerator {
public IqPacket generateSetUnblockRequest(final Jid jid) {
final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
final Element block = iq.addChild("unblock", Namespace.BLOCKING);
block.addChild("item").setAttribute("jid", jid.asBareJid().toString());
block.addChild("item").setAttribute("jid", jid.toEscapedString());
return iq;
}
@ -423,29 +423,60 @@ public class IqGenerator extends AbstractGenerator {
}
public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId) {
IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
return pushTokenToAppServer(appServer, token, deviceId, null);
}
public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId, Jid muc) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
packet.setTo(appServer);
Element command = packet.addChild("command", "http://jabber.org/protocol/commands");
final Element command = packet.addChild("command", Namespace.COMMANDS);
command.setAttribute("node", "register-push-fcm");
command.setAttribute("action", "execute");
Data data = new Data();
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) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
packet.setTo(appServer);
final Element command = packet.addChild("command", Namespace.COMMANDS);
command.setAttribute("node", "unregister-push-fcm");
command.setAttribute("action", "execute");
final Data data = new Data();
data.put("channel", channel);
data.put("android-id", deviceId);
data.submit();
command.addChild(data);
return packet;
}
public IqPacket enablePush(Jid jid, String node, String secret) {
public IqPacket enablePush(final Jid jid, final String node, final String secret) {
IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
Element enable = packet.addChild("enable", "urn:xmpp:push:0");
Element enable = packet.addChild("enable", Namespace.PUSH);
enable.setAttribute("jid", jid.toString());
enable.setAttribute("node", node);
Data data = new Data();
data.setFormType(Namespace.PUBSUB_PUBLISH_OPTIONS);
data.put("secret", secret);
data.submit();
enable.addChild(data);
if (secret != null) {
Data data = new Data();
data.setFormType(Namespace.PUBSUB_PUBLISH_OPTIONS);
data.put("secret", secret);
data.submit();
enable.addChild(data);
}
return packet;
}
public IqPacket disablePush(final Jid jid, final String node) {
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;
}

View File

@ -26,6 +26,7 @@ import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xml.Element;
@ -37,360 +38,383 @@ import rocks.xmpp.addr.Jid;
public class IqParser extends AbstractParser implements OnIqPacketReceived {
public IqParser(final XmppConnectionService service) {
super(service);
}
public IqParser(final XmppConnectionService service) {
super(service);
}
private void rosterItems(final Account account, final Element query) {
final String version = query.getAttribute("ver");
if (version != null) {
account.getRoster().setVersion(version);
}
for (final Element item : query.getChildren()) {
if (item.getName().equals("item")) {
final Jid jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid"));
if (jid == null) {
continue;
}
final String name = item.getAttribute("name");
final String subscription = item.getAttribute("subscription");
final Contact contact = account.getRoster().getContact(jid);
boolean bothPre = contact.getOption(Contact.Options.TO) && contact.getOption(Contact.Options.FROM);
if (!contact.getOption(Contact.Options.DIRTY_PUSH)) {
contact.setServerName(name);
contact.parseGroupsFromElement(item);
}
if ("remove".equals(subscription)) {
contact.resetOption(Contact.Options.IN_ROSTER);
contact.resetOption(Contact.Options.DIRTY_DELETE);
contact.resetOption(Contact.Options.PREEMPTIVE_GRANT);
} else {
contact.setOption(Contact.Options.IN_ROSTER);
contact.resetOption(Contact.Options.DIRTY_PUSH);
contact.parseSubscriptionFromElement(item);
}
boolean both = contact.getOption(Contact.Options.TO) && contact.getOption(Contact.Options.FROM);
if ((both != bothPre) && both) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": gained mutual presence subscription with "+contact.getJid());
AxolotlService axolotlService = account.getAxolotlService();
if (axolotlService != null) {
axolotlService.clearErrorsInFetchStatusMap(contact.getJid());
}
}
mXmppConnectionService.getAvatarService().clear(contact);
}
}
mXmppConnectionService.updateConversationUi();
mXmppConnectionService.updateRosterUi();
mXmppConnectionService.getShortcutService().refresh();
mXmppConnectionService.syncRoster(account);
}
private void rosterItems(final Account account, final Element query) {
final String version = query.getAttribute("ver");
if (version != null) {
account.getRoster().setVersion(version);
}
for (final Element item : query.getChildren()) {
if (item.getName().equals("item")) {
final Jid jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid"));
if (jid == null) {
continue;
}
final String name = item.getAttribute("name");
final String subscription = item.getAttribute("subscription");
final Contact contact = account.getRoster().getContact(jid);
boolean bothPre = contact.getOption(Contact.Options.TO) && contact.getOption(Contact.Options.FROM);
if (!contact.getOption(Contact.Options.DIRTY_PUSH)) {
contact.setServerName(name);
contact.parseGroupsFromElement(item);
}
if ("remove".equals(subscription)) {
contact.resetOption(Contact.Options.IN_ROSTER);
contact.resetOption(Contact.Options.DIRTY_DELETE);
contact.resetOption(Contact.Options.PREEMPTIVE_GRANT);
} else {
contact.setOption(Contact.Options.IN_ROSTER);
contact.resetOption(Contact.Options.DIRTY_PUSH);
contact.parseSubscriptionFromElement(item);
}
boolean both = contact.getOption(Contact.Options.TO) && contact.getOption(Contact.Options.FROM);
if ((both != bothPre) && both) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": gained mutual presence subscription with " + contact.getJid());
AxolotlService axolotlService = account.getAxolotlService();
if (axolotlService != null) {
axolotlService.clearErrorsInFetchStatusMap(contact.getJid());
}
}
mXmppConnectionService.getAvatarService().clear(contact);
}
}
mXmppConnectionService.updateConversationUi();
mXmppConnectionService.updateRosterUi();
mXmppConnectionService.getShortcutService().refresh();
mXmppConnectionService.syncRoster(account);
}
public String avatarData(final IqPacket packet) {
final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB);
if (pubsub == null) {
return null;
}
final Element items = pubsub.findChild("items");
if (items == null) {
return null;
}
return super.avatarData(items);
}
public String avatarData(final IqPacket packet) {
final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB);
if (pubsub == null) {
return null;
}
final Element items = pubsub.findChild("items");
if (items == null) {
return null;
}
return super.avatarData(items);
}
public Element getItem(final IqPacket packet) {
final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB);
if (pubsub == null) {
return null;
}
final Element items = pubsub.findChild("items");
if (items == null) {
return null;
}
return items.findChild("item");
}
public Element getItem(final IqPacket packet) {
final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB);
if (pubsub == null) {
return null;
}
final Element items = pubsub.findChild("items");
if (items == null) {
return null;
}
return items.findChild("item");
}
@NonNull
public Set<Integer> deviceIds(final Element item) {
Set<Integer> deviceIds = new HashSet<>();
if (item != null) {
final Element list = item.findChild("list");
if (list != null) {
for (Element device : list.getChildren()) {
if (!device.getName().equals("device")) {
continue;
}
try {
Integer id = Integer.valueOf(device.getAttribute("id"));
deviceIds.add(id);
} catch (NumberFormatException e) {
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Encountered invalid <device> node in PEP ("+e.getMessage()+"):" + device.toString()+ ", skipping...");
continue;
}
}
}
}
return deviceIds;
}
@NonNull
public Set<Integer> deviceIds(final Element item) {
Set<Integer> deviceIds = new HashSet<>();
if (item != null) {
final Element list = item.findChild("list");
if (list != null) {
for (Element device : list.getChildren()) {
if (!device.getName().equals("device")) {
continue;
}
try {
Integer id = Integer.valueOf(device.getAttribute("id"));
deviceIds.add(id);
} catch (NumberFormatException e) {
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Encountered invalid <device> node in PEP (" + e.getMessage() + "):" + device.toString() + ", skipping...");
continue;
}
}
}
}
return deviceIds;
}
public Integer signedPreKeyId(final Element bundle) {
final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic");
if(signedPreKeyPublic == null) {
return null;
}
try {
return Integer.valueOf(signedPreKeyPublic.getAttribute("signedPreKeyId"));
} catch (NumberFormatException e) {
return null;
}
}
public Integer signedPreKeyId(final Element bundle) {
final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic");
if (signedPreKeyPublic == null) {
return null;
}
try {
return Integer.valueOf(signedPreKeyPublic.getAttribute("signedPreKeyId"));
} catch (NumberFormatException e) {
return null;
}
}
public ECPublicKey signedPreKeyPublic(final Element bundle) {
ECPublicKey publicKey = null;
final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic");
if(signedPreKeyPublic == null) {
return null;
}
try {
publicKey = Curve.decodePoint(Base64.decode(signedPreKeyPublic.getContent(),Base64.DEFAULT), 0);
} catch (Throwable e) {
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Invalid signedPreKeyPublic in PEP: " + e.getMessage());
}
return publicKey;
}
public ECPublicKey signedPreKeyPublic(final Element bundle) {
ECPublicKey publicKey = null;
final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic");
if (signedPreKeyPublic == null) {
return null;
}
try {
publicKey = Curve.decodePoint(Base64.decode(signedPreKeyPublic.getContent(), Base64.DEFAULT), 0);
} catch (Throwable e) {
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Invalid signedPreKeyPublic in PEP: " + e.getMessage());
}
return publicKey;
}
public byte[] signedPreKeySignature(final Element bundle) {
final Element signedPreKeySignature = bundle.findChild("signedPreKeySignature");
if(signedPreKeySignature == null) {
return null;
}
try {
return Base64.decode(signedPreKeySignature.getContent(), Base64.DEFAULT);
} catch (Throwable e) {
Log.e(Config.LOGTAG,AxolotlService.LOGPREFIX+" : Invalid base64 in signedPreKeySignature");
return null;
}
}
public byte[] signedPreKeySignature(final Element bundle) {
final Element signedPreKeySignature = bundle.findChild("signedPreKeySignature");
if (signedPreKeySignature == null) {
return null;
}
try {
return Base64.decode(signedPreKeySignature.getContent(), Base64.DEFAULT);
} catch (Throwable e) {
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : Invalid base64 in signedPreKeySignature");
return null;
}
}
public IdentityKey identityKey(final Element bundle) {
IdentityKey identityKey = null;
final Element identityKeyElement = bundle.findChild("identityKey");
if(identityKeyElement == null) {
return null;
}
try {
identityKey = new IdentityKey(Base64.decode(identityKeyElement.getContent(), Base64.DEFAULT), 0);
} catch (Throwable e) {
Log.e(Config.LOGTAG,AxolotlService.LOGPREFIX+" : "+"Invalid identityKey in PEP: "+e.getMessage());
}
return identityKey;
}
public IdentityKey identityKey(final Element bundle) {
IdentityKey identityKey = null;
final Element identityKeyElement = bundle.findChild("identityKey");
if (identityKeyElement == null) {
return null;
}
try {
identityKey = new IdentityKey(Base64.decode(identityKeyElement.getContent(), Base64.DEFAULT), 0);
} catch (Throwable e) {
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Invalid identityKey in PEP: " + e.getMessage());
}
return identityKey;
}
public Map<Integer, ECPublicKey> preKeyPublics(final IqPacket packet) {
Map<Integer, ECPublicKey> preKeyRecords = new HashMap<>();
Element item = getItem(packet);
if (item == null) {
Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Couldn't find <item> in bundle IQ packet: " + packet);
return null;
}
final Element bundleElement = item.findChild("bundle");
if(bundleElement == null) {
return null;
}
final Element prekeysElement = bundleElement.findChild("prekeys");
if(prekeysElement == null) {
Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Couldn't find <prekeys> in bundle IQ packet: " + packet);
return null;
}
for(Element preKeyPublicElement : prekeysElement.getChildren()) {
if(!preKeyPublicElement.getName().equals("preKeyPublic")){
Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Encountered unexpected tag in prekeys list: " + preKeyPublicElement);
continue;
}
Integer preKeyId = null;
try {
preKeyId = Integer.valueOf(preKeyPublicElement.getAttribute("preKeyId"));
final ECPublicKey preKeyPublic = Curve.decodePoint(Base64.decode(preKeyPublicElement.getContent(), Base64.DEFAULT), 0);
preKeyRecords.put(preKeyId, preKeyPublic);
} catch (NumberFormatException e) {
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"could not parse preKeyId from preKey "+preKeyPublicElement.toString());
} catch (Throwable e) {
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Invalid preKeyPublic (ID="+preKeyId+") in PEP: "+ e.getMessage()+", skipping...");
}
}
return preKeyRecords;
}
public Map<Integer, ECPublicKey> preKeyPublics(final IqPacket packet) {
Map<Integer, ECPublicKey> preKeyRecords = new HashMap<>();
Element item = getItem(packet);
if (item == null) {
Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Couldn't find <item> in bundle IQ packet: " + packet);
return null;
}
final Element bundleElement = item.findChild("bundle");
if (bundleElement == null) {
return null;
}
final Element prekeysElement = bundleElement.findChild("prekeys");
if (prekeysElement == null) {
Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Couldn't find <prekeys> in bundle IQ packet: " + packet);
return null;
}
for (Element preKeyPublicElement : prekeysElement.getChildren()) {
if (!preKeyPublicElement.getName().equals("preKeyPublic")) {
Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Encountered unexpected tag in prekeys list: " + preKeyPublicElement);
continue;
}
Integer preKeyId = null;
try {
preKeyId = Integer.valueOf(preKeyPublicElement.getAttribute("preKeyId"));
final ECPublicKey preKeyPublic = Curve.decodePoint(Base64.decode(preKeyPublicElement.getContent(), Base64.DEFAULT), 0);
preKeyRecords.put(preKeyId, preKeyPublic);
} catch (NumberFormatException e) {
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "could not parse preKeyId from preKey " + preKeyPublicElement.toString());
} catch (Throwable e) {
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Invalid preKeyPublic (ID=" + preKeyId + ") in PEP: " + e.getMessage() + ", skipping...");
}
}
return preKeyRecords;
}
public Pair<X509Certificate[],byte[]> verification(final IqPacket packet) {
Element item = getItem(packet);
Element verification = item != null ? item.findChild("verification",AxolotlService.PEP_PREFIX) : null;
Element chain = verification != null ? verification.findChild("chain") : null;
Element signature = verification != null ? verification.findChild("signature") : null;
if (chain != null && signature != null) {
List<Element> certElements = chain.getChildren();
X509Certificate[] certificates = new X509Certificate[certElements.size()];
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
int i = 0;
for(Element cert : certElements) {
certificates[i] = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(Base64.decode(cert.getContent(),Base64.DEFAULT)));
++i;
}
return new Pair<>(certificates,Base64.decode(signature.getContent(),Base64.DEFAULT));
} catch (CertificateException e) {
return null;
}
} else {
return null;
}
}
public Pair<X509Certificate[], byte[]> verification(final IqPacket packet) {
Element item = getItem(packet);
Element verification = item != null ? item.findChild("verification", AxolotlService.PEP_PREFIX) : null;
Element chain = verification != null ? verification.findChild("chain") : null;
Element signature = verification != null ? verification.findChild("signature") : null;
if (chain != null && signature != null) {
List<Element> certElements = chain.getChildren();
X509Certificate[] certificates = new X509Certificate[certElements.size()];
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
int i = 0;
for (Element cert : certElements) {
certificates[i] = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(Base64.decode(cert.getContent(), Base64.DEFAULT)));
++i;
}
return new Pair<>(certificates, Base64.decode(signature.getContent(), Base64.DEFAULT));
} catch (CertificateException e) {
return null;
}
} else {
return null;
}
}
public PreKeyBundle bundle(final IqPacket bundle) {
Element bundleItem = getItem(bundle);
if(bundleItem == null) {
return null;
}
final Element bundleElement = bundleItem.findChild("bundle");
if(bundleElement == null) {
return null;
}
ECPublicKey signedPreKeyPublic = signedPreKeyPublic(bundleElement);
Integer signedPreKeyId = signedPreKeyId(bundleElement);
byte[] signedPreKeySignature = signedPreKeySignature(bundleElement);
IdentityKey identityKey = identityKey(bundleElement);
if(signedPreKeyId == null || signedPreKeyPublic == null || identityKey == null) {
return null;
}
public PreKeyBundle bundle(final IqPacket bundle) {
Element bundleItem = getItem(bundle);
if (bundleItem == null) {
return null;
}
final Element bundleElement = bundleItem.findChild("bundle");
if (bundleElement == null) {
return null;
}
ECPublicKey signedPreKeyPublic = signedPreKeyPublic(bundleElement);
Integer signedPreKeyId = signedPreKeyId(bundleElement);
byte[] signedPreKeySignature = signedPreKeySignature(bundleElement);
IdentityKey identityKey = identityKey(bundleElement);
if (signedPreKeyId == null || signedPreKeyPublic == null || identityKey == null) {
return null;
}
return new PreKeyBundle(0, 0, 0, null,
signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey);
}
return new PreKeyBundle(0, 0, 0, null,
signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey);
}
public List<PreKeyBundle> preKeys(final IqPacket preKeys) {
List<PreKeyBundle> bundles = new ArrayList<>();
Map<Integer, ECPublicKey> preKeyPublics = preKeyPublics(preKeys);
if ( preKeyPublics != null) {
for (Integer preKeyId : preKeyPublics.keySet()) {
ECPublicKey preKeyPublic = preKeyPublics.get(preKeyId);
bundles.add(new PreKeyBundle(0, 0, preKeyId, preKeyPublic,
0, null, null, null));
}
}
public List<PreKeyBundle> preKeys(final IqPacket preKeys) {
List<PreKeyBundle> bundles = new ArrayList<>();
Map<Integer, ECPublicKey> preKeyPublics = preKeyPublics(preKeys);
if (preKeyPublics != null) {
for (Integer preKeyId : preKeyPublics.keySet()) {
ECPublicKey preKeyPublic = preKeyPublics.get(preKeyId);
bundles.add(new PreKeyBundle(0, 0, preKeyId, preKeyPublic,
0, null, null, null));
}
}
return bundles;
}
return bundles;
}
@Override
public void onIqPacketReceived(final Account account, final IqPacket packet) {
final boolean isGet = packet.getType() == IqPacket.TYPE.GET;
if (packet.getType() == IqPacket.TYPE.ERROR || packet.getType() == IqPacket.TYPE.TIMEOUT) {
return;
}
if (packet.hasChild("query", Namespace.ROSTER) && packet.fromServer(account)) {
final Element query = packet.findChild("query");
// If this is in response to a query for the whole roster:
if (packet.getType() == IqPacket.TYPE.RESULT) {
account.getRoster().markAllAsNotInRoster();
}
this.rosterItems(account, query);
} else if ((packet.hasChild("block", Namespace.BLOCKING) || packet.hasChild("blocklist", Namespace.BLOCKING)) &&
packet.fromServer(account)) {
// Block list or block push.
Log.d(Config.LOGTAG, "Received blocklist update from server");
final Element blocklist = packet.findChild("blocklist", Namespace.BLOCKING);
final Element block = packet.findChild("block", Namespace.BLOCKING);
final Collection<Element> items = blocklist != null ? blocklist.getChildren() :
(block != null ? block.getChildren() : null);
// If this is a response to a blocklist query, clear the block list and replace with the new one.
// Otherwise, just update the existing blocklist.
if (packet.getType() == IqPacket.TYPE.RESULT) {
account.clearBlocklist();
account.getXmppConnection().getFeatures().setBlockListRequested(true);
}
if (items != null) {
final Collection<Jid> jids = new ArrayList<>(items.size());
// Create a collection of Jids from the packet
for (final Element item : items) {
if (item.getName().equals("item")) {
final Jid jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid"));
if (jid != null) {
jids.add(jid);
}
}
}
account.getBlocklist().addAll(jids);
if (packet.getType() == IqPacket.TYPE.SET) {
boolean removed = false;
for(Jid jid : jids) {
removed |= mXmppConnectionService.removeBlockedConversations(account,jid);
}
if (removed) {
mXmppConnectionService.updateConversationUi();
}
}
}
// Update the UI
mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED);
if (packet.getType() == IqPacket.TYPE.SET) {
final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT);
mXmppConnectionService.sendIqPacket(account, response, null);
}
} else if (packet.hasChild("unblock", Namespace.BLOCKING) &&
packet.fromServer(account) && packet.getType() == IqPacket.TYPE.SET) {
Log.d(Config.LOGTAG, "Received unblock update from server");
final Collection<Element> items = packet.findChild("unblock", Namespace.BLOCKING).getChildren();
if (items.size() == 0) {
// No children to unblock == unblock all
account.getBlocklist().clear();
} else {
final Collection<Jid> jids = new ArrayList<>(items.size());
for (final Element item : items) {
if (item.getName().equals("item")) {
final Jid jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid"));
if (jid != null) {
jids.add(jid);
}
}
}
account.getBlocklist().removeAll(jids);
}
mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED);
final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT);
mXmppConnectionService.sendIqPacket(account, response, null);
} else if (packet.hasChild("open", "http://jabber.org/protocol/ibb")
|| packet.hasChild("data", "http://jabber.org/protocol/ibb")
|| packet.hasChild("close","http://jabber.org/protocol/ibb")) {
mXmppConnectionService.getJingleConnectionManager()
.deliverIbbPacket(account, packet);
} else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) {
final IqPacket response = mXmppConnectionService.getIqGenerator().discoResponse(account, packet);
mXmppConnectionService.sendIqPacket(account, response, null);
} else if (packet.hasChild("query","jabber:iq:version") && isGet) {
final IqPacket response = mXmppConnectionService.getIqGenerator().versionResponse(packet);
mXmppConnectionService.sendIqPacket(account,response,null);
} else if (packet.hasChild("ping", "urn:xmpp:ping") && isGet) {
final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT);
mXmppConnectionService.sendIqPacket(account, response, null);
} else if (packet.hasChild("time","urn:xmpp:time") && isGet) {
final IqPacket response;
if (mXmppConnectionService.useTorToConnect() || account.isOnion()) {
response = packet.generateResponse(IqPacket.TYPE.ERROR);
final Element error = response.addChild("error");
error.setAttribute("type","cancel");
error.addChild("not-allowed","urn:ietf:params:xml:ns:xmpp-stanzas");
} else {
response = mXmppConnectionService.getIqGenerator().entityTimeResponse(packet);
}
mXmppConnectionService.sendIqPacket(account,response, null);
} else {
if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) {
final IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR);
final Element error = response.addChild("error");
error.setAttribute("type", "cancel");
error.addChild("feature-not-implemented","urn:ietf:params:xml:ns:xmpp-stanzas");
account.getXmppConnection().sendIqPacket(response, null);
}
}
}
@Override
public void onIqPacketReceived(final Account account, final IqPacket packet) {
final boolean isGet = packet.getType() == IqPacket.TYPE.GET;
if (packet.getType() == IqPacket.TYPE.ERROR || packet.getType() == IqPacket.TYPE.TIMEOUT) {
return;
}
if (packet.hasChild("query", Namespace.ROSTER) && packet.fromServer(account)) {
final Element query = packet.findChild("query");
// If this is in response to a query for the whole roster:
if (packet.getType() == IqPacket.TYPE.RESULT) {
account.getRoster().markAllAsNotInRoster();
}
this.rosterItems(account, query);
} else if ((packet.hasChild("block", Namespace.BLOCKING) || packet.hasChild("blocklist", Namespace.BLOCKING)) &&
packet.fromServer(account)) {
// Block list or block push.
Log.d(Config.LOGTAG, "Received blocklist update from server");
final Element blocklist = packet.findChild("blocklist", Namespace.BLOCKING);
final Element block = packet.findChild("block", Namespace.BLOCKING);
final Collection<Element> items = blocklist != null ? blocklist.getChildren() :
(block != null ? block.getChildren() : null);
// If this is a response to a blocklist query, clear the block list and replace with the new one.
// Otherwise, just update the existing blocklist.
if (packet.getType() == IqPacket.TYPE.RESULT) {
account.clearBlocklist();
account.getXmppConnection().getFeatures().setBlockListRequested(true);
}
if (items != null) {
final Collection<Jid> jids = new ArrayList<>(items.size());
// Create a collection of Jids from the packet
for (final Element item : items) {
if (item.getName().equals("item")) {
final Jid jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid"));
if (jid != null) {
jids.add(jid);
}
}
}
account.getBlocklist().addAll(jids);
if (packet.getType() == IqPacket.TYPE.SET) {
boolean removed = false;
for (Jid jid : jids) {
removed |= mXmppConnectionService.removeBlockedConversations(account, jid);
}
if (removed) {
mXmppConnectionService.updateConversationUi();
}
}
}
// Update the UI
mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED);
if (packet.getType() == IqPacket.TYPE.SET) {
final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT);
mXmppConnectionService.sendIqPacket(account, response, null);
}
} else if (packet.hasChild("unblock", Namespace.BLOCKING) &&
packet.fromServer(account) && packet.getType() == IqPacket.TYPE.SET) {
Log.d(Config.LOGTAG, "Received unblock update from server");
final Collection<Element> items = packet.findChild("unblock", Namespace.BLOCKING).getChildren();
if (items.size() == 0) {
// No children to unblock == unblock all
account.getBlocklist().clear();
} else {
final Collection<Jid> jids = new ArrayList<>(items.size());
for (final Element item : items) {
if (item.getName().equals("item")) {
final Jid jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid"));
if (jid != null) {
jids.add(jid);
}
}
}
account.getBlocklist().removeAll(jids);
}
mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED);
final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT);
mXmppConnectionService.sendIqPacket(account, response, null);
} else if (packet.hasChild("open", "http://jabber.org/protocol/ibb")
|| packet.hasChild("data", "http://jabber.org/protocol/ibb")
|| packet.hasChild("close", "http://jabber.org/protocol/ibb")) {
mXmppConnectionService.getJingleConnectionManager()
.deliverIbbPacket(account, packet);
} else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) {
final IqPacket response = mXmppConnectionService.getIqGenerator().discoResponse(account, packet);
mXmppConnectionService.sendIqPacket(account, response, null);
} else if (packet.hasChild("query", "jabber:iq:version") && isGet) {
final IqPacket response = mXmppConnectionService.getIqGenerator().versionResponse(packet);
mXmppConnectionService.sendIqPacket(account, response, null);
} else if (packet.hasChild("ping", "urn:xmpp:ping") && isGet) {
final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT);
mXmppConnectionService.sendIqPacket(account, response, null);
} else if (packet.hasChild("time", "urn:xmpp:time") && isGet) {
final IqPacket response;
if (mXmppConnectionService.useTorToConnect() || account.isOnion()) {
response = packet.generateResponse(IqPacket.TYPE.ERROR);
final Element error = response.addChild("error");
error.setAttribute("type", "cancel");
error.addChild("not-allowed", "urn:ietf:params:xml:ns:xmpp-stanzas");
} else {
response = mXmppConnectionService.getIqGenerator().entityTimeResponse(packet);
}
mXmppConnectionService.sendIqPacket(account, response, null);
} else if (packet.hasChild("pubsub", Namespace.PUBSUB) && packet.getType() == IqPacket.TYPE.SET) {
final Jid server = packet.getFrom();
final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB);
final Element publish = pubsub == null ? null : pubsub.findChild("publish");
final String node = publish == null ? null : publish.getAttribute("node");
final Element item = publish == null ? null : publish.findChild("item");
final Element notification = item == null ? null : item.findChild("notification", Namespace.PUSH);
if (notification != null && node != null && server != null) {
final Conversation conversation = mXmppConnectionService.findConversationByUuid(node);
if (conversation != null && conversation.getAccount() == account && conversation.getJid().getDomain().equals(server.getDomain())) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": received muc push event for "+conversation.getJid().asBareJid());
mXmppConnectionService.sendIqPacket(account, packet.generateResponse(IqPacket.TYPE.RESULT), null);
mXmppConnectionService.mucSelfPingAndRejoin(conversation);
} else {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": received push event for unknown conference from "+server);
final IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR);
final Element error = response.addChild("error");
error.setAttribute("type", "cancel");
error.addChild("item-not-found", "urn:ietf:params:xml:ns:xmpp-stanzas");
mXmppConnectionService.sendIqPacket(account, response, null);
}
}
} else {
if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) {
final IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR);
final Element error = response.addChild("error");
error.setAttribute("type", "cancel");
error.addChild("feature-not-implemented", "urn:ietf:params:xml:ns:xmpp-stanzas");
account.getXmppConnection().sendIqPacket(response, null);
}
}
}
}

View File

@ -22,6 +22,7 @@ import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Conversational;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.ReadByMarker;
@ -126,7 +127,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
service.reportBrokenSessionException(e, postpone);
return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
} else {
Log.d(Config.LOGTAG,"ignoring broken session exception because checkForDuplicase failed");
Log.d(Config.LOGTAG,"ignoring broken session exception because checkForDuplicates failed");
//TODO should be still emit a failed message?
return null;
}
} catch (NotEncryptedForThisDeviceException e) {
@ -264,6 +266,17 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
packet.getId(),
Message.STATUS_SEND_FAILED,
extractErrorMessage(packet));
final Element error = packet.findChild("error");
final boolean pingWorthyError = error != null && (error.hasChild("not-acceptable") || error.hasChild("remote-server-timeout") || error.hasChild("remote-server-not-found"));
if (pingWorthyError) {
Conversation conversation = mXmppConnectionService.find(account,from);
if (conversation != null && conversation.getMode() == Conversational.MODE_MULTI) {
if (conversation.getMucOptions().online()) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": received ping worthy error for seemingly online muc at "+from);
mXmppConnectionService.mucSelfPingAndRejoin(conversation);
}
}
}
}
return true;
}
@ -437,6 +450,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
origin = from;
}
//TODO either or is probably fine?
final boolean checkedForDuplicates = serverMsgId != null && remoteMsgId != null && !conversation.possibleDuplicate(serverMsgId, remoteMsgId);
if (origin != null) {
@ -598,7 +612,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
} else {
serverMsgIdUpdated = false;
}
Log.d(Config.LOGTAG, "skipping duplicate message with " + message.getCounterpart() + ". serverMsgIdUpdated=" + Boolean.toString(serverMsgIdUpdated));
Log.d(Config.LOGTAG, "skipping duplicate message with " + message.getCounterpart() + ". serverMsgIdUpdated=" + serverMsgIdUpdated);
return;
}
}

View File

@ -341,7 +341,7 @@ public class FileBackend {
}
}
public static void close(Closeable stream) {
public static void close(final Closeable stream) {
if (stream != null) {
try {
stream.close();
@ -350,7 +350,7 @@ public class FileBackend {
}
}
public static void close(Socket socket) {
public static void close(final Socket socket) {
if (socket != null) {
try {
socket.close();

View File

@ -37,6 +37,7 @@ import eu.siacs.conversations.entities.Conversational;
import eu.siacs.conversations.entities.ListItem;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.RawBlockable;
import eu.siacs.conversations.http.services.MuclumbusService;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
@ -272,7 +273,9 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
}
public Bitmap get(ListItem item, int size, boolean cachedOnly) {
if (item instanceof Contact) {
if (item instanceof RawBlockable) {
return get(item.getDisplayName(), item.getJid().toEscapedString(), size, cachedOnly);
} else if (item instanceof Contact) {
return get((Contact) item, size, cachedOnly);
} else if (item instanceof Bookmark) {
Bookmark bookmark = (Bookmark) item;

View File

@ -314,6 +314,12 @@ public class XmppConnectionService extends Service {
}
account.getRoster().clearPresences();
synchronized (account.inProgressConferenceJoins) {
account.inProgressConferenceJoins.clear();
}
synchronized (account.inProgressConferencePings) {
account.inProgressConferencePings.clear();
}
mJingleConnectionManager.cancelInTransmission();
mQuickConversationsService.considerSyncBackground(false);
fetchRosterFromServer(account);
@ -372,18 +378,37 @@ public class XmppConnectionService extends Service {
}
List<Conversation> conversations = getConversations();
for (Conversation conversation : conversations) {
if (conversation.getAccount() == account && !account.pendingConferenceJoins.contains(conversation)) {
final boolean inProgressJoin;
synchronized (account.inProgressConferenceJoins) {
inProgressJoin = account.inProgressConferenceJoins.contains(conversation);
}
final boolean pendingJoin;
synchronized (account.pendingConferenceJoins) {
pendingJoin = account.pendingConferenceJoins.contains(conversation);
}
if (conversation.getAccount() == account
&& !pendingJoin
&& !inProgressJoin) {
sendUnsentMessages(conversation);
}
}
for (Conversation conversation : account.pendingConferenceLeaves) {
final List<Conversation> pendingLeaves;
synchronized (account.pendingConferenceLeaves) {
pendingLeaves = new ArrayList<>(account.pendingConferenceLeaves);
account.pendingConferenceLeaves.clear();
}
for (Conversation conversation : pendingLeaves) {
leaveMuc(conversation);
}
account.pendingConferenceLeaves.clear();
for (Conversation conversation : account.pendingConferenceJoins) {
final List<Conversation> pendingJoins;
synchronized (account.pendingConferenceJoins) {
pendingJoins = new ArrayList<>(account.pendingConferenceJoins);
account.pendingConferenceJoins.clear();
}
for (Conversation conversation : pendingJoins) {
joinMuc(conversation);
}
account.pendingConferenceJoins.clear();
scheduleWakeUpCall(Config.PING_MAX_INTERVAL, account.getUuid().hashCode());
} else if (account.getStatus() == Account.State.OFFLINE || account.getStatus() == Account.State.DISABLED) {
resetSendingToWaiting(account);
@ -586,6 +611,7 @@ public class XmppConnectionService extends Service {
toggleForegroundService(true);
}
String pushedAccountHash = null;
String pushedChannelHash = null;
boolean interactive = false;
if (action != null) {
final String uuid = intent.getStringExtra("uuid");
@ -698,6 +724,7 @@ public class XmppConnectionService extends Service {
break;
case ACTION_FCM_MESSAGE_RECEIVED:
pushedAccountHash = intent.getStringExtra("account");
pushedChannelHash = intent.getStringExtra("channel");
Log.d(Config.LOGTAG, "push message arrived in service. account=" + pushedAccountHash);
break;
case Intent.ACTION_SEND:
@ -711,13 +738,18 @@ public class XmppConnectionService extends Service {
synchronized (this) {
WakeLockHelper.acquire(wakeLock);
boolean pingNow = ConnectivityManager.CONNECTIVITY_ACTION.equals(action) || (Config.POST_CONNECTIVITY_CHANGE_PING_INTERVAL > 0 && ACTION_POST_CONNECTIVITY_CHANGE.equals(action));
HashSet<Account> pingCandidates = new HashSet<>();
final HashSet<Account> pingCandidates = new HashSet<>();
final String androidId = PhoneHelper.getAndroidId(this);
for (Account account : accounts) {
final boolean pushWasMeantForThisAccount = CryptoHelper.getAccountFingerprint(account, androidId).equals(pushedAccountHash);
pingNow |= processAccountState(account,
interactive,
"ui".equals(action),
CryptoHelper.getAccountFingerprint(account, PhoneHelper.getAndroidId(this)).equals(pushedAccountHash),
pushWasMeantForThisAccount,
pingCandidates);
if (pushWasMeantForThisAccount && pushedChannelHash != null) {
checkMucStillJoined(account, pushedAccountHash, androidId);
}
}
if (pingNow) {
for (Account account : pingCandidates) {
@ -810,6 +842,20 @@ public class XmppConnectionService extends Service {
return pingNow;
}
private void checkMucStillJoined(final Account account, final String hash, final String androidId) {
for(final Conversation conversation : this.conversations) {
if (conversation.getAccount() == account && conversation.getMode() == Conversational.MODE_MULTI) {
Jid jid = conversation.getJid().asBareJid();
final String currentHash = CryptoHelper.getFingerprint(jid, androidId);
if (currentHash.equals(hash)) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": received cloud push notification for MUC "+jid);
return;
}
}
}
mPushManagementService.unregisterChannel(account, hash);
}
public void reinitializeMuclumbusService() {
mChannelDiscoveryService.initializeMuclumbusService();
}
@ -848,7 +894,7 @@ public class XmppConnectionService extends Service {
}
@Override
public void userInputRequried(PendingIntent pi, Message object) {
public void userInputRequired(PendingIntent pi, Message object) {
}
});
@ -1347,7 +1393,12 @@ public class XmppConnectionService extends Service {
}
}
if (account.isOnlineAndConnected()) {
final boolean inProgressJoin;
synchronized (account.inProgressConferenceJoins) {
inProgressJoin = conversation.getMode() == Conversational.MODE_MULTI && account.inProgressConferenceJoins.contains(conversation);
}
if (account.isOnlineAndConnected() && !inProgressJoin) {
switch (message.getEncryption()) {
case Message.ENCRYPTION_NONE:
if (message.needsUploading()) {
@ -1995,6 +2046,10 @@ public class XmppConnectionService extends Service {
}
}
}
if (conversation.getMucOptions().push()) {
disableDirectMucPush(conversation);
mPushManagementService.disablePushOnServer(conversation);
}
leaveMuc(conversation);
} else {
if (conversation.getContact().getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
@ -2432,21 +2487,37 @@ public class XmppConnectionService extends Service {
}
public void mucSelfPingAndRejoin(final Conversation conversation) {
final Account account = conversation.getAccount();
synchronized (account.inProgressConferenceJoins) {
if (account.inProgressConferenceJoins.contains(conversation)) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": canceling muc self ping because join is already under way");
return;
}
}
synchronized (account.inProgressConferencePings) {
if (!account.inProgressConferencePings.add(conversation)) {
Log.d(Config.LOGTAG, account.getJid().asBareJid()+": canceling muc self ping because ping is already under way");
return;
}
}
final Jid self = conversation.getMucOptions().getSelf().getFullJid();
final IqPacket ping = new IqPacket(IqPacket.TYPE.GET);
ping.setTo(self);
ping.addChild("ping", Namespace.PING);
sendIqPacket(conversation.getAccount(), ping, (account, response) -> {
sendIqPacket(conversation.getAccount(), ping, (a, response) -> {
if (response.getType() == IqPacket.TYPE.ERROR) {
Element error = response.findChild("error");
if (error == null || error.hasChild("service-unavailable") || error.hasChild("feature-not-implemented") || error.hasChild("item-not-found")) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": ping to "+self+" came back as ignorable error");
Log.d(Config.LOGTAG,a.getJid().asBareJid()+": ping to "+self+" came back as ignorable error");
} else {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": ping to "+self+" failed. attempting rejoin");
Log.d(Config.LOGTAG,a.getJid().asBareJid()+": ping to "+self+" failed. attempting rejoin");
joinMuc(conversation);
}
} else if (response.getType() == IqPacket.TYPE.RESULT) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": ping to "+self+" came back fine");
Log.d(Config.LOGTAG,a.getJid().asBareJid()+": ping to "+self+" came back fine");
}
synchronized (account.inProgressConferencePings) {
account.inProgressConferencePings.remove(conversation);
}
});
}
@ -2464,10 +2535,17 @@ public class XmppConnectionService extends Service {
}
private void joinMuc(Conversation conversation, final OnConferenceJoined onConferenceJoined, final boolean followedInvite) {
Account account = conversation.getAccount();
account.pendingConferenceJoins.remove(conversation);
account.pendingConferenceLeaves.remove(conversation);
final Account account = conversation.getAccount();
synchronized (account.pendingConferenceJoins) {
account.pendingConferenceJoins.remove(conversation);
}
synchronized (account.pendingConferenceLeaves) {
account.pendingConferenceLeaves.remove(conversation);
}
if (account.getStatus() == Account.State.ONLINE) {
synchronized (account.inProgressConferenceJoins) {
account.inProgressConferenceJoins.add(conversation);
}
sendPresencePacket(account, mPresenceGenerator.leave(conversation.getMucOptions()));
conversation.resetMucOptions();
if (onConferenceJoined != null) {
@ -2523,7 +2601,13 @@ public class XmppConnectionService extends Service {
saveConversationAsBookmark(conversation, null);
}
}
sendUnsentMessages(conversation);
if (mucOptions.push()) {
enableMucPush(conversation);
}
synchronized (account.inProgressConferenceJoins) {
account.inProgressConferenceJoins.remove(conversation);
sendUnsentMessages(conversation);
}
}
@Override
@ -2539,9 +2623,13 @@ public class XmppConnectionService extends Service {
public void onFetchFailed(final Conversation conversation, Element error) {
if (conversation.getStatus() == Conversation.STATUS_ARCHIVED) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": conversation ("+conversation.getJid()+") got archived before IQ result");
return;
}
if (error != null && "remote-server-not-found".equals(error.getName())) {
synchronized (account.inProgressConferenceJoins) {
account.inProgressConferenceJoins.remove(conversation);
}
conversation.getMucOptions().setError(MucOptions.Error.SERVER_NOT_FOUND);
updateConversationUi();
} else {
@ -2552,13 +2640,48 @@ public class XmppConnectionService extends Service {
});
updateConversationUi();
} else {
account.pendingConferenceJoins.add(conversation);
synchronized (account.pendingConferenceJoins) {
account.pendingConferenceJoins.add(conversation);
}
conversation.resetMucOptions();
conversation.setHasMessagesLeftOnServer(false);
updateConversationUi();
}
}
private void enableDirectMucPush(final Conversation conversation) {
final Account account = conversation.getAccount();
final Jid room = conversation.getJid().asBareJid();
final IqPacket enable = mIqGenerator.enablePush(conversation.getAccount().getJid(), conversation.getUuid(), null);
enable.setTo(room);
sendIqPacket(account, enable, (a, response) -> {
if (response.getType() == IqPacket.TYPE.RESULT) {
Log.d(Config.LOGTAG,a.getJid().asBareJid()+": enabled direct push for muc "+room);
} else if (response.getType() == IqPacket.TYPE.ERROR) {
Log.d(Config.LOGTAG,a.getJid().asBareJid()+": unable to enable direct push for muc "+room+" "+response.getError());
}
});
}
private void enableMucPush(final Conversation conversation) {
enableDirectMucPush(conversation);
mPushManagementService.registerPushTokenOnServer(conversation);
}
private void disableDirectMucPush(final Conversation conversation) {
final Account account = conversation.getAccount();
final Jid room = conversation.getJid().asBareJid();
final IqPacket disable = mIqGenerator.disablePush(conversation.getAccount().getJid(), conversation.getUuid());
disable.setTo(room);
sendIqPacket(account, disable, (a, response) -> {
if (response.getType() == IqPacket.TYPE.RESULT) {
Log.d(Config.LOGTAG,a.getJid().asBareJid()+": disabled direct push for muc "+room);
} else if (response.getType() == IqPacket.TYPE.ERROR) {
Log.d(Config.LOGTAG,a.getJid().asBareJid()+": unable to disable direct push for muc "+room+" "+response.getError());
}
});
}
private void fetchConferenceMembers(final Conversation conversation) {
final Account account = conversation.getAccount();
final AxolotlService axolotlService = account.getAxolotlService();
@ -2734,9 +2857,13 @@ public class XmppConnectionService extends Service {
}
private void leaveMuc(Conversation conversation, boolean now) {
Account account = conversation.getAccount();
account.pendingConferenceJoins.remove(conversation);
account.pendingConferenceLeaves.remove(conversation);
final Account account = conversation.getAccount();
synchronized (account.pendingConferenceJoins) {
account.pendingConferenceJoins.remove(conversation);
}
synchronized (account.pendingConferenceLeaves) {
account.pendingConferenceLeaves.remove(conversation);
}
if (account.getStatus() == Account.State.ONLINE || now) {
sendPresencePacket(conversation.getAccount(), mPresenceGenerator.leave(conversation.getMucOptions()));
conversation.getMucOptions().setOffline();
@ -2746,7 +2873,9 @@ public class XmppConnectionService extends Service {
}
Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": leaving muc " + conversation.getJid());
} else {
account.pendingConferenceLeaves.add(conversation);
synchronized (account.pendingConferenceLeaves) {
account.pendingConferenceLeaves.add(conversation);
}
}
}
@ -4007,6 +4136,7 @@ public class XmppConnectionService extends Service {
for (Account account : getAccounts()) {
if (account.isOnlineAndConnected() && mPushManagementService.available(account)) {
mPushManagementService.registerPushTokenOnServer(account);
//TODO renew mucs
}
}
}
@ -4121,17 +4251,15 @@ public class XmppConnectionService extends Service {
public boolean sendBlockRequest(final Blockable blockable, boolean reportSpam) {
if (blockable != null && blockable.getBlockedJid() != null) {
final Jid jid = blockable.getBlockedJid();
this.sendIqPacket(blockable.getAccount(), getIqGenerator().generateSetBlockRequest(jid, reportSpam), new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(final Account account, final IqPacket packet) {
if (packet.getType() == IqPacket.TYPE.RESULT) {
account.getBlocklist().add(jid);
updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED);
}
}
});
if (removeBlockedConversations(blockable.getAccount(), jid)) {
this.sendIqPacket(blockable.getAccount(), getIqGenerator().generateSetBlockRequest(jid, reportSpam), (a, response) -> {
if (response.getType() == IqPacket.TYPE.RESULT) {
a.getBlocklist().add(jid);
updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED);
}
});
if (blockable.getBlockedJid().isFullJid()) {
return false;
} else if (removeBlockedConversations(blockable.getAccount(), jid)) {
updateConversationUi();
return true;
} else {

View File

@ -28,14 +28,18 @@ public final class BlockContactDialog {
final String value;
@StringRes int res;
if (blockable.getJid().getLocal() == null || blockable.getAccount().isBlocked(Jid.ofDomain(blockable.getJid().getDomain()))) {
if (blockable.getJid().isFullJid()) {
builder.setTitle(isBlocked ? R.string.action_unblock_participant : R.string.action_block_participant);
value = blockable.getJid().toEscapedString();
res = isBlocked ? R.string.unblock_contact_text : R.string.block_contact_text;
} else if (blockable.getJid().getLocal() == null || blockable.getAccount().isBlocked(Jid.ofDomain(blockable.getJid().getDomain()))) {
builder.setTitle(isBlocked ? R.string.action_unblock_domain : R.string.action_block_domain);
value = Jid.ofDomain(blockable.getJid().getDomain()).toString();
res = isBlocked ? R.string.unblock_domain_text : R.string.block_domain_text;
} else {
int resBlockAction = blockable instanceof Conversation && ((Conversation) blockable).isWithStranger() ? R.string.block_stranger : R.string.action_block_contact;
builder.setTitle(isBlocked ? R.string.action_unblock_contact : resBlockAction);
value = blockable.getJid().asBareJid().toString();
value = blockable.getJid().asBareJid().toEscapedString();
res = isBlocked ? R.string.unblock_contact_text : R.string.block_contact_text;
}
binding.text.setText(JidDialog.style(xmppActivity, res, value));

View File

@ -10,7 +10,10 @@ import java.util.Collections;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Blockable;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.ListItem;
import eu.siacs.conversations.entities.RawBlockable;
import eu.siacs.conversations.ui.interfaces.OnBackendConnected;
import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
import rocks.xmpp.addr.Jid;
@ -23,7 +26,7 @@ public class BlocklistActivity extends AbstractSearchableListItemActivity implem
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getListView().setOnItemLongClickListener((parent, view, position, id) -> {
BlockContactDialog.show(BlocklistActivity.this, (Contact) getListItems().get(position));
BlockContactDialog.show(BlocklistActivity.this, (Blockable) getListItems().get(position));
return true;
});
this.binding.fab.show();
@ -50,9 +53,14 @@ public class BlocklistActivity extends AbstractSearchableListItemActivity implem
getListItems().clear();
if (account != null) {
for (final Jid jid : account.getBlocklist()) {
final Contact contact = account.getRoster().getContact(jid);
if (contact.match(this, needle) && contact.isBlocked()) {
getListItems().add(contact);
ListItem item;
if (jid.isFullJid()) {
item = new RawBlockable(account, jid);
} else {
item = account.getRoster().getContact(jid);
}
if (item.match(this, needle)) {
getListItems().add(item);
}
}
Collections.sort(getListItems());
@ -78,8 +86,8 @@ public class BlocklistActivity extends AbstractSearchableListItemActivity implem
);
dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid) -> {
Contact contact = account.getRoster().getContact(contactJid);
if (xmppConnectionService.sendBlockRequest(contact, false)) {
Blockable blockable = new RawBlockable(account, contactJid);
if (xmppConnectionService.sendBlockRequest(blockable, false)) {
Toast.makeText(BlocklistActivity.this, R.string.corresponding_conversations_closed, Toast.LENGTH_SHORT).show();
}
return true;
@ -101,4 +109,5 @@ public class BlocklistActivity extends AbstractSearchableListItemActivity implem
public void OnUpdateBlocklist(final OnUpdateBlocklist.Status status) {
refreshUi();
}
}

View File

@ -83,7 +83,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
}
@Override
public void userInputRequried(PendingIntent pi, Conversation object) {
public void userInputRequired(PendingIntent pi, Conversation object) {
}
};

View File

@ -632,7 +632,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
@Override
public void userInputRequried(PendingIntent pi, Message object) {
public void userInputRequired(PendingIntent pi, Message object) {
}
});
@ -666,7 +666,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
@Override
public void userInputRequried(PendingIntent pi, Message message) {
public void userInputRequired(PendingIntent pi, Message message) {
hidePrepareFileToast(prepareFileToast);
}
});
@ -688,7 +688,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
new UiCallback<Message>() {
@Override
public void userInputRequried(PendingIntent pi, Message object) {
public void userInputRequired(PendingIntent pi, Message object) {
hidePrepareFileToast(prepareFileToast);
}
@ -1326,7 +1326,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
new UiCallback<Contact>() {
@Override
public void userInputRequried(PendingIntent pi, Contact contact) {
public void userInputRequired(PendingIntent pi, Contact contact) {
startPendingIntent(pi, attachmentChoice);
}
@ -2284,7 +2284,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
status = Presence.Status.OFFLINE;
}
this.binding.textSendButton.setTag(action);
this.binding.textSendButton.setImageResource(SendButtonTool.getSendButtonImageResource(getActivity(), action, status));
final Activity activity = getActivity();
if (activity != null) {
this.binding.textSendButton.setImageResource(SendButtonTool.getSendButtonImageResource(activity, action, status));
}
}
protected void updateStatusMessages() {
@ -2456,7 +2459,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
new UiCallback<Contact>() {
@Override
public void userInputRequried(PendingIntent pi, Contact contact) {
public void userInputRequired(PendingIntent pi, Contact contact) {
startPendingIntent(pi, REQUEST_ENCRYPT_MESSAGE);
}
@ -2512,7 +2515,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
new UiCallback<Message>() {
@Override
public void userInputRequried(PendingIntent pi, Message message) {
public void userInputRequired(PendingIntent pi, Message message) {
startPendingIntent(pi, REQUEST_SEND_MESSAGE);
}

View File

@ -104,7 +104,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
private final UiCallback<Avatar> mAvatarFetchCallback = new UiCallback<Avatar>() {
@Override
public void userInputRequried(final PendingIntent pi, final Avatar avatar) {
public void userInputRequired(final PendingIntent pi, final Avatar avatar) {
finishInitialSetup(avatar);
}
@ -917,7 +917,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
}
@Override
public void userInputRequried(PendingIntent pi, String object) {
public void userInputRequired(PendingIntent pi, String object) {
mPendingPresenceTemplate.push(template);
try {
startIntentSenderForResult(pi.getIntentSender(), REQUEST_CHANGE_STATUS, null, 0, 0, 0);

View File

@ -23,8 +23,10 @@ import org.osmdroid.api.IGeoPoint;
import org.osmdroid.api.IMapController;
import org.osmdroid.config.Configuration;
import org.osmdroid.config.IConfigurationProvider;
import org.osmdroid.tileprovider.tilesource.TileSourceFactory;
import org.osmdroid.tileprovider.tilesource.XYTileSource;
import org.osmdroid.util.GeoPoint;
import org.osmdroid.views.CustomZoomButtonsController;
import org.osmdroid.views.MapView;
import org.osmdroid.views.overlay.Overlay;
@ -72,15 +74,7 @@ public abstract class LocationActivity extends ActionBarActivity implements Loca
protected void updateLocationMarkers() {
clearMarkers();
}
protected XYTileSource tileSource() {
return new XYTileSource("OpenStreetMap",
0, 19, 256, ".png", new String[] {
"https://a.tile.openstreetmap.org/",
"https://b.tile.openstreetmap.org/",
"https://c.tile.openstreetmap.org/" },"© OpenStreetMap contributors");
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -103,7 +97,7 @@ public abstract class LocationActivity extends ActionBarActivity implements Loca
final IConfigurationProvider config = Configuration.getInstance();
config.load(ctx, getPreferences());
config.setUserAgentValue(BuildConfig.APPLICATION_ID + "_" + BuildConfig.VERSION_CODE);
config.setUserAgentValue(BuildConfig.APPLICATION_ID + "/" + BuildConfig.VERSION_CODE);
if (QuickConversationsService.isConversations() && getBooleanPreference("use_tor", R.bool.use_tor)) {
try {
config.setHttpProxy(HttpConnectionManager.getProxy());
@ -111,17 +105,6 @@ public abstract class LocationActivity extends ActionBarActivity implements Loca
throw new RuntimeException("Unable to configure proxy");
}
}
final File f = new File(ctx.getCacheDir() + "/tiles");
try {
//noinspection ResultOfMethodCallIgnored
f.mkdirs();
} catch (final SecurityException ignored) {
}
if (f.exists() && f.isDirectory() && f.canRead() && f.canWrite()) {
Log.d(Config.LOGTAG, "Using tile cache at: " + f.getAbsolutePath());
config.setOsmdroidTileCache(f.getAbsoluteFile());
}
}
@Override
@ -150,8 +133,8 @@ public abstract class LocationActivity extends ActionBarActivity implements Loca
protected void setupMapView(MapView mapView, final GeoPoint pos) {
map = mapView;
map.setTileSource(tileSource());
map.setBuiltInZoomControls(false);
map.setTileSource(TileSourceFactory.MAPNIK);
map.getZoomController().setVisibility(CustomZoomButtonsController.Visibility.NEVER);
map.setMultiTouchControls(true);
map.setTilesScaledToDpi(true);
mapController = map.getController();
@ -251,7 +234,7 @@ public abstract class LocationActivity extends ActionBarActivity implements Loca
requestLocationUpdates();
updateLocationMarkers();
updateUi();
map.setTileSource(tileSource());
map.setTileSource(TileSourceFactory.MAPNIK);
map.setTilesScaledToDpi(true);
if (mapAtInitialLoc()) {

View File

@ -174,7 +174,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
}
@Override
public void userInputRequried(PendingIntent pi, Conversation object) {
public void userInputRequired(PendingIntent pi, Conversation object) {
}
};
@ -1085,7 +1085,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
}
@Override
public void userInputRequried(PendingIntent pi, Conversation object) {
public void userInputRequired(PendingIntent pi, Conversation object) {
}
});

View File

@ -7,5 +7,5 @@ public interface UiCallback<T> {
void error(int errorCode, T object);
void userInputRequried(PendingIntent pi, T object);
void userInputRequired(PendingIntent pi, T object);
}

View File

@ -137,7 +137,7 @@ public abstract class XmppActivity extends ActionBarActivity {
}
@Override
public void userInputRequried(PendingIntent pi, Conversation object) {
public void userInputRequired(PendingIntent pi, Conversation object) {
}
};
@ -565,7 +565,7 @@ public abstract class XmppActivity extends ActionBarActivity {
xmppConnectionService.getPgpEngine().generateSignature(intent, account, status, new UiCallback<String>() {
@Override
public void userInputRequried(PendingIntent pi, String signature) {
public void userInputRequired(PendingIntent pi, String signature) {
try {
startIntentSenderForResult(pi.getIntentSender(), REQUEST_ANNOUNCE_PGP, null, 0, 0, 0);
} catch (final SendIntentException ignored) {
@ -625,7 +625,7 @@ public abstract class XmppActivity extends ActionBarActivity {
}
@Override
public void userInputRequried(PendingIntent pi, Account object) {
public void userInputRequired(PendingIntent pi, Account object) {
try {
startIntentSenderForResult(pi.getIntentSender(),
REQUEST_CHOOSE_PGP_ID, null, 0, 0, 0);

View File

@ -246,8 +246,12 @@ public final class CryptoHelper {
return prettifyFingerprintCert(bytesToHex(fingerprint));
}
public static String getFingerprint(Jid jid, String androidId) {
return getFingerprint(jid.toEscapedString() + "\00" + androidId);
}
public static String getAccountFingerprint(Account account, String androidId) {
return getFingerprint(account.getJid().asBareJid().toEscapedString() + "\00" + androidId);
return getFingerprint(account.getJid().asBareJid(), androidId);
}
public static String getFingerprint(String value) {

View File

@ -7,7 +7,7 @@ import org.hsluv.HUSLColorConverter;
import java.security.MessageDigest;
public class XEP0392Helper {
class XEP0392Helper {
private static double angle(String nickname) {
try {
@ -20,7 +20,7 @@ public class XEP0392Helper {
}
}
public static int rgbFromNick(String name) {
static int rgbFromNick(String name) {
double[] hsluv = new double[3];
hsluv[0] = angle(name) * 360;
hsluv[1] = 100;

View File

@ -28,4 +28,6 @@ public final class Namespace {
public static final String JINGLE_TRANSPORTS_S5B = "urn:xmpp:jingle:transports:s5b:1";
public static final String JINGLE_TRANSPORTS_IBB = "urn:xmpp:jingle:transports:ibb:1";
public static final String PING = "urn:xmpp:ping";
public static final String PUSH = "urn:xmpp:push:0";
public static final String COMMANDS = "http://jabber.org/protocol/commands";
}

View File

@ -6,14 +6,15 @@ import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import eu.siacs.conversations.Config;
public class XmlReader {
private XmlPullParser parser;
public class XmlReader implements Closeable {
private final XmlPullParser parser;
private InputStream is;
public XmlReader() {
@ -48,6 +49,11 @@ public class XmlReader {
}
}
@Override
public void close() {
this.is = null;
}
public Tag readTag() throws IOException {
try {
while (this.is != null && parser.next() != XmlPullParser.END_DOCUMENT) {

View File

@ -1439,15 +1439,8 @@ public class XmppConnection implements Runnable {
}
private void forceCloseSocket() {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": io exception " + e.getMessage() + " during force close");
}
} else {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": socket was null during force close");
}
FileBackend.close(this.socket);
FileBackend.close(this.tagReader);
}
public void interrupt() {
@ -1458,7 +1451,7 @@ public class XmppConnection implements Runnable {
public void disconnect(final boolean force) {
interrupt();
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": disconnecting force=" + Boolean.toString(force));
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": disconnecting force=" + force);
if (force) {
forceCloseSocket();
} else {
@ -1798,8 +1791,8 @@ public class XmppConnection implements Runnable {
}
public boolean push() {
return hasDiscoFeature(account.getJid().asBareJid(), "urn:xmpp:push:0")
|| hasDiscoFeature(Jid.of(account.getServer()), "urn:xmpp:push:0");
return hasDiscoFeature(account.getJid().asBareJid(), Namespace.PUSH)
|| hasDiscoFeature(Jid.of(account.getServer()), Namespace.PUSH);
}
public boolean rosterVersioning() {

View File

@ -776,4 +776,10 @@
<string name="open_with">Отваряне с…</string>
<string name="set_profile_picture">Профилна снимка за Conversations</string>
<string name="choose_account">Изберете профил</string>
<string name="restore_backup">Възстановяване от резервно копие</string>
<string name="restore">Възстановяване</string>
<string name="enter_password_to_restore">Въведете паролата си за профила %s, за да направите възстановяване от резервно копие.</string>
<string name="restore_warning">Не използвайте възможността за възстановяване от резервно копие, за да клонирате (да изпълнявате едновременно) инсталацията. Възстановяването от резервно копие е предназначено за мигриране или в случай, че сте загубили устройството си.</string>
<string name="unable_to_restore_backup">Не може да се направи възстановяване от резервно копие.</string>
<string name="unable_to_decrypt_backup">Резервното копие не може да бъде дешифрирано. Правилна ли е паролата?</string>
</resources>

View File

@ -17,6 +17,8 @@
<string name="action_unblock_contact">Kontakt entsperren</string>
<string name="action_block_domain">Domain sperren</string>
<string name="action_unblock_domain">Domain entsperren</string>
<string name="action_block_participant">Teilnehmer sperren</string>
<string name="action_unblock_participant">Teilnehmer entsperren</string>
<string name="title_activity_manage_accounts">Konten verwalten</string>
<string name="title_activity_settings">Einstellungen</string>
<string name="title_activity_sharewith">Mit Unterhaltung teilen</string>

View File

@ -17,6 +17,8 @@
<string name="action_unblock_contact">Desbloquear contacto</string>
<string name="action_block_domain">Bloquear dominio</string>
<string name="action_unblock_domain">Desbloquear dominio</string>
<string name="action_block_participant">Bloquear persoa</string>
<string name="action_unblock_participant">Desbloquear persoa</string>
<string name="title_activity_manage_accounts">Xestionar contas</string>
<string name="title_activity_settings">Axustes</string>
<string name="title_activity_sharewith">Compartir na conversa</string>

View File

@ -17,6 +17,8 @@
<string name="action_unblock_contact">Deblochează contact</string>
<string name="action_block_domain">Blochează domeniu</string>
<string name="action_unblock_domain">Deblochează domeniu</string>
<string name="action_block_participant">Blochează participant</string>
<string name="action_unblock_participant">Deblochează participant</string>
<string name="title_activity_manage_accounts">Configurează conturile</string>
<string name="title_activity_settings">Setări</string>
<string name="title_activity_sharewith">Partajează într-o conversație</string>

View File

@ -3,8 +3,10 @@
<string name="action_settings">Настройки</string>
<string name="action_add">Новая беседа</string>
<string name="action_accounts">Управление аккаунтами</string>
<string name="action_end_conversation">Закрыть текущую беседу</string>
<string name="action_contact_details">Сведения о контакте</string>
<string name="action_muc_details">Подробности конференции</string>
<string name="channel_details">Сведения о канале</string>
<string name="action_secure">Защищённая беседа</string>
<string name="action_add_account">Добавить аккаунт</string>
<string name="action_edit_contact">Редактировать контакт</string>
@ -48,6 +50,7 @@
<string name="share_with">Поделиться с</string>
<string name="start_conversation">Начать беседу</string>
<string name="invite_contact">Пригласить собеседника</string>
<string name="invite">Пригласить</string>
<string name="contacts">Контакты</string>
<string name="contact">Контакт</string>
<string name="cancel">Отмена</string>
@ -78,6 +81,7 @@
<string name="clear_histor_msg">Вы хотите удалить все сообщения в этой беседе?\n\n<b>Предупреждение:</b> Данная операция не повлияет на сообщения, хранящиеся на других устройствах или серверах.</string>
<string name="delete_file_dialog">Удалить файл</string>
<string name="delete_file_dialog_msg">Вы уверены, что хотите удалить этот файл?\n\n<b>Предупреждение:</b> Данная операция не удалит копии этого файла, хранящиеся на других устройствах или серверах.</string>
<string name="also_end_conversation">Закрыть эту беседу</string>
<string name="choose_presence">Выберите устройство</string>
<string name="send_unencrypted_message">Нешифрованное сообщение</string>
<string name="send_message">Сообщение</string>
@ -158,15 +162,18 @@
<string name="mgmt_account_disable">Временно отключить</string>
<string name="mgmt_account_publish_avatar">Разместить аватар</string>
<string name="mgmt_account_publish_pgp">Анонсировать OpenPGP ключ</string>
<string name="unpublish_pgp">Удалить открытый OpenPGP ключ</string>
<string name="unpublish_pgp">Удалить открытый ключ OpenPGP</string>
<string name="unpublish_pgp_message">Вы действительно хотите удалить ваш OpenPGP публичный ключ из опубликованных?\nВаши собеседники не смогут больше отправлять вам зашифрованные OpenPGP сообщения.</string>
<string name="openpgp_has_been_published">Открытый ключ OpenPGP был опубликован.</string>
<string name="mgmt_account_enable">Включить аккаунт</string>
<string name="mgmt_account_are_you_sure">Вы уверены?</string>
<string name="mgmt_account_delete_confirm_text">Если вы удалите аккаунт, будет потеряна вся история переписки.</string>
<string name="attach_record_voice">Запись голоса</string>
<string name="account_settings_jabber_id">XMPP-адрес</string>
<string name="block_jabber_id">Заблокировать XMPP-адрес</string>
<string name="account_settings_example_jabber_id">username@example.com</string>
<string name="password">Пароль</string>
<string name="invalid_jid">Недопустимый XMPP-адрес</string>
<string name="error_out_of_memory">Недостаточно памяти. Изображение слишком большое</string>
<string name="add_phone_book_text">Вы хотите добавить %s в вашу адресную книгу?</string>
<string name="server_info_show_more">Информация о сервере</string>
@ -201,6 +208,7 @@
<string name="fetching_keys">Получение ключей…</string>
<string name="done">Готово</string>
<string name="decrypt">Расшифровать</string>
<string name="bookmarks">Закладки</string>
<string name="search">Поиск</string>
<string name="enter_contact">Добавить контакт</string>
<string name="delete_contact">Удалить контакт</string>
@ -213,6 +221,8 @@
<string name="join">Присоединиться</string>
<string name="save_as_bookmark">Сохранить закладку</string>
<string name="delete_bookmark">Удалить закладку</string>
<string name="destroy_room">Уничтожить конференцию</string>
<string name="destroy_channel">Уничтожить канал</string>
<string name="bookmark_already_exists">Такая закладка уже существует</string>
<string name="action_edit_subject">Редактировать тему конференции</string>
<string name="topic">Тема</string>
@ -253,6 +263,7 @@
<string name="pref_allow_message_correction_summary">Позволить контактам редактировать сообщения</string>
<string name="pref_expert_options">Расширенные настройки</string>
<string name="pref_expert_options_summary">Пожалуйста, будьте осторожны с данными настройками</string>
<string name="title_activity_about_x">О %s</string>
<string name="title_pref_quiet_hours">Тихие часы</string>
<string name="title_pref_quiet_hours_start_time">Начало</string>
<string name="title_pref_quiet_hours_end_time">Окончание</string>
@ -281,6 +292,9 @@
<string name="send_again">Отправить ещё раз</string>
<string name="file_url">URL файла</string>
<string name="url_copied_to_clipboard">Ссылка скопирована в буфер обмена</string>
<string name="jabber_id_copied_to_clipboard">XMPP-адрес скопирован в буфер обмена</string>
<string name="error_message_copied_to_clipboard">Сообщение об ошибке скопировано в буфер обмена</string>
<string name="web_address">веб-адрес</string>
<string name="scan_qr_code">Сканировать 2D штрихкод</string>
<string name="show_qr_code">Показать 2D штрихкод</string>
<string name="show_block_list">Показать чёрный список</string>
@ -289,6 +303,14 @@
<string name="try_again">Повторить</string>
<string name="pref_keep_foreground_service">Оставить службу на переднем плане</string>
<string name="pref_keep_foreground_service_summary">Не позволяет операционной системе закрыть ваше соединение</string>
<string name="pref_create_backup">Создать резервную копию</string>
<string name="pref_create_backup_summary">Файлы резервной копии будут сохранены в %s</string>
<string name="notification_create_backup_title">Создание резервной копии</string>
<string name="notification_backup_created_title">Ваша резервная копия была создана</string>
<string name="notification_backup_created_subtitle">Файлы резервной копии сохранены в %s</string>
<string name="restoring_backup">Восстановление из резервной копии</string>
<string name="notification_restored_backup_title">Восстановление из резервной копии выполнено</string>
<string name="notification_restored_backup_subtitle">Не забудьте включить аккаунт</string>
<string name="choose_file">Выбрать файл</string>
<string name="receiving_x_file">%1$s загружается (%2$d%% выполнено)</string>
<string name="download_x_file">Загрузить %s</string>
@ -311,7 +333,7 @@
<string name="conference_creation_failed">Не удалось создать конференцию!</string>
<string name="account_image_description">Аватар аккаунта</string>
<string name="copy_omemo_clipboard_description">Скопировать OMEMO-отпечаток в буфер обмена</string>
<string name="regenerate_omemo_key">Создать заново ключ OMEMO</string>
<string name="regenerate_omemo_key">Создать ключ OMEMO заново</string>
<string name="clear_other_devices">Очистить устройства</string>
<string name="clear_other_devices_desc">Вы уверены, что хотите очистить все остальные устройства из анонса ключей OMEMO? При соединении устройств в следующий раз новые ключи анонсируются автоматически, но устройства могут не получить сообщения, посланные до этого.</string>
<string name="error_no_keys_to_trust_server_error">Для этого контакта не существует доступных ключей.\nПопытка получения новых ключей с сервера оказалась неудачной. Возможно, что-то не так с сервером вашего собеседника.</string>
@ -366,8 +388,8 @@
<string name="hide_offline">Скрыть пользователей вне сети</string>
<string name="contact_is_typing">%s печатает…</string>
<string name="contact_has_stopped_typing">%s прекратил набор</string>
<string name="contacts_are_typing">%s набирает...</string>
<string name="contacts_have_stopped_typing">%s перестал печатать</string>
<string name="contacts_are_typing">%s печатают...</string>
<string name="contacts_have_stopped_typing">%s перестали печатать</string>
<string name="pref_chat_states">Оповещения о наборе</string>
<string name="pref_chat_states_summary">Позволяет вашим контактам видеть когда вы пишете им новое сообщение</string>
<string name="send_location">Отправить местоположение</string>
@ -395,7 +417,8 @@
<string name="recently_used">Последнее выбранное</string>
<string name="choose_quick_action">Выбрать быстрое действие</string>
<string name="search_contacts">Поиск контактов</string>
<string name="send_private_message">Отправить частное сообщение</string>
<string name="search_bookmarks">Поиск закладок</string>
<string name="send_private_message">Отправить личное сообщение</string>
<string name="user_has_left_conference">%1$s покинул конференцию!</string>
<string name="username">Имя пользователя</string>
<string name="username_hint">Имя пользователя</string>
@ -409,8 +432,8 @@
<string name="account_status_host_unknown">Сервер не ответственен за домен</string>
<string name="server_info_broken">Повреждено</string>
<string name="pref_presence_settings">Доступность</string>
<string name="pref_away_when_screen_off">Вышел когда экран выключен</string>
<string name="pref_away_when_screen_off_summary">Отмечает ваш ресурс как «вышел» когда экран выключен</string>
<string name="pref_away_when_screen_off">\"Отошёл\" когда экран выключен</string>
<string name="pref_away_when_screen_off_summary">Отмечает ваш ресурс как «отошёл» когда экран выключен</string>
<string name="pref_dnd_on_silent_mode">\"Не беспокоить\" в беззвучном режиме</string>
<string name="pref_dnd_on_silent_mode_summary">Помечать ресурс как \"Не беспокоить\", когда устройство в беззвучном режиме</string>
<string name="pref_treat_vibrate_as_silent">Не доступен в режиме вибрации</string>
@ -430,7 +453,7 @@
<string name="certificate_chain_is_not_trusted">Цепочка сертификата не доверена</string>
<string name="action_renew_certificate">Обновить сертификат</string>
<string name="error_fetching_omemo_key">Ошибка при получении OMEMO ключа!</string>
<string name="verified_omemo_key_with_certificate">Проверен OMEMO ключ с сертификатом!</string>
<string name="verified_omemo_key_with_certificate">Ключ OMEMO проверен с сертификатом!</string>
<string name="device_does_not_support_certificates">Ваше устройство не поддерживает выбор клиентских сертификатов!</string>
<string name="pref_connection_options">Подключение</string>
<string name="pref_use_tor">Соединение через Tor</string>
@ -459,6 +482,8 @@
<string name="notify_only_when_highlighted">Уведомлять только при упоминании</string>
<string name="notify_never">Без уведомления</string>
<string name="notify_paused">Уведомления приостановлены</string>
<string name="pref_picture_compression">Сжатие изображений</string>
<string name="pref_picture_compression_summary">Изменять размер и сжимать изображения</string>
<string name="always">Всегда</string>
<string name="automatically">Автоматически</string>
<string name="battery_optimizations_enabled">Оптимизации энергопотребления разрешены</string>
@ -475,9 +500,12 @@
<string name="security_error_invalid_file_access">Ошибка безопасности: недействительный доступ к файлу</string>
<string name="no_application_to_share_uri">Не найдено приложения для отправки</string>
<string name="share_uri_with">Отправить URI…</string>
<string name="your_full_jid_will_be">Ваш полный XMPP-адрес будет: %s</string>
<string name="create_account">Создать аккаунт</string>
<string name="use_own_provider">Использовать свой провайдер</string>
<string name="pick_your_username">Выберите имя пользователя</string>
<string name="pref_manually_change_presence">Управлять доступностью вручную</string>
<string name="pref_manually_change_presence_summary">Устанавливать свою доступность при редактировании статусного сообщения</string>
<string name="status_message">Статусное собщение</string>
<string name="presence_chat">Свободен для общения</string>
<string name="presence_online">В сети</string>
@ -491,6 +519,7 @@
<string name="choose_participants">Выбрать участников</string>
<string name="creating_conference">Создание конференции…</string>
<string name="invite_again">Пригласить ещё раз</string>
<string name="gp_disable">Выключен</string>
<string name="gp_short">Короткий</string>
<string name="gp_medium">Средний</string>
<string name="gp_long">Длинный</string>
@ -506,7 +535,7 @@
<string name="type_pc">Компьютер</string>
<string name="type_phone">Телефон</string>
<string name="type_tablet">Планшет</string>
<string name="type_web">Веб браузер</string>
<string name="type_web">Веб-браузер</string>
<string name="type_console">Консоль</string>
<string name="payment_required">Требуется оплата</string>
<string name="missing_internet_permission">Доступ в интернет запрещён</string>
@ -548,10 +577,10 @@
<string name="pref_clean_private_storage_summary">Очистить закрытое хранилище, где хранятся файлы (Файлы можно заново скачать с сервера)</string>
<string name="i_followed_this_link_from_a_trusted_source">Открывать ссылки из надёжного источника</string>
<string name="verifying_omemo_keys_trusted_source">Вы потвердите OMEMO ключи %1$s после нажатия на ссылку. Это безопасно только если вы перешли по ссылке из доверенного источника, где только %2$sмог разместить эту ссылку.</string>
<string name="verify_omemo_keys">Проверить OMEMO ключ</string>
<string name="verify_omemo_keys">Проверить OMEMO-ключи</string>
<string name="show_inactive_devices">Показывать неактивные</string>
<string name="hide_inactive_devices">Скрыть неактивные</string>
<string name="distrust_omemo_key">Не доверенное устройство.</string>
<string name="distrust_omemo_key">Прекратить доверять устройству</string>
<string name="distrust_omemo_key_text">Вы действительно хотите удалить устройство из доверенных?\Устройство и сообщения, полученные с этого устройства, будут помечаться как недоверенные.</string>
<plurals name="seconds">
<item quantity="one">%d секунда</item>
@ -618,7 +647,10 @@
<string name="copy_to_clipboard">Скопировать в буфер обмена</string>
<string name="message_copied_to_clipboard">Сообщение скопировано в буфер обмена</string>
<string name="message">Сообщение</string>
<string name="private_messages_are_disabled">Личные сообщения выключены</string>
<string name="mtm_accept_cert">Принять Неизвестный Сертификат?</string>
<string name="pref_scroll_to_bottom">Прокручивать вниз</string>
<string name="pref_scroll_to_bottom_summary">Прокручивать вниз после отправки сообщения</string>
<string name="edit_status_message_title">Редактировать статусное сообщение</string>
<string name="edit_status_message">Редактировать статусное сообщение</string>
<string name="disable_encryption">Отключить шифрование</string>
@ -626,16 +658,43 @@
<string name="disable_now">Отключить сейчас</string>
<string name="draft">Черновик:</string>
<string name="pref_omemo_setting">OMEMO-шифрование</string>
<string name="pref_omemo_setting_summary_always">OMEMO будет всегда использоваться для одиночных бесед и закрытых конференций.</string>
<string name="pref_omemo_setting_summary_default_on">OMEMO будет использоваться по умолчанию для новых бесед.</string>
<string name="pref_omemo_setting_summary_default_off">OMEMO нужно будет явно включать для новых бесед.</string>
<string name="pref_font_size">Размер шрифта</string>
<string name="pref_font_size_summary">Относительный размер шрифта используемый в приложении.</string>
<string name="default_on">Включено по умолчанию</string>
<string name="default_off">Выключено по умолчанию</string>
<string name="small">Маленький</string>
<string name="medium">Средний</string>
<string name="large">Большой</string>
<string name="not_encrypted_for_this_device">Сообщение не зашифровано для этого устройства.</string>
<string name="omemo_decryption_failed">Не удалось расшифровать OMEMO-сообщение.</string>
<string name="undo">отменить</string>
<string name="action_copy_location">Копировать местоположение</string>
<string name="action_share_location">Поделиться местоположением</string>
<string name="title_activity_share_location">Поделиться местоположением</string>
<string name="title_activity_show_location">Показать местоположение</string>
<string name="share">Поделиться</string>
<string name="unable_to_start_recording">Невозможно начать запись</string>
<string name="please_wait">Пожалуйста, подождите…</string>
<string name="no_microphone_permission">Conversations нужен доступ к микрофону</string>
<string name="search_messages">Поиск сообщений</string>
<string name="view_conversation">Посмотреть беседу</string>
<string name="copy_link">Копировать веб-адрес</string>
<string name="copy_jabber_id">Копировать XMPP-адрес</string>
<string name="pref_start_search">Быстрый поиск</string>
<string name="pref_start_search_summary">На экране \"Начать беседу\" открывать клавиатуру и ставить курсор в поле поиска</string>
<string name="pref_more_notification_settings">Настройки уведомлений</string>
<string name="view_media">Просмотр медиа</string>
<string name="pref_video_compression">Качество видео</string>
<string name="pref_video_compression_summary">Низкое качество означает меньшие файлы</string>
<string name="video_360p">Среднее (360p)</string>
<string name="video_720p">Высокое (720р)</string>
<string name="video_original">Оригинал (без сжатия)</string>
<string name="create_group_chat">Создать конференцию</string>
<string name="join_public_channel">Присоединиться к каналу</string>
<string name="create_private_group_chat">Создать закрытую конференцию</string>
<string name="create_public_channel">Создать публичный канал</string>
<string name="discover_channels">Найти каналы</string>
</resources>

View File

@ -17,6 +17,8 @@
<string name="action_unblock_contact">Unblock contact</string>
<string name="action_block_domain">Block domain</string>
<string name="action_unblock_domain">Unblock domain</string>
<string name="action_block_participant">Block participant</string>
<string name="action_unblock_participant">Unblock participant</string>
<string name="title_activity_manage_accounts">Manage Accounts</string>
<string name="title_activity_settings">Settings</string>
<string name="title_activity_sharewith">Share with Conversation</string>

View File

@ -5,13 +5,16 @@ import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.InstanceIdResult;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.utils.PhoneHelper;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
import eu.siacs.conversations.xmpp.XmppConnection;
import eu.siacs.conversations.xmpp.forms.Data;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
@ -19,82 +22,161 @@ import rocks.xmpp.addr.Jid;
public class PushManagementService {
protected final XmppConnectionService mXmppConnectionService;
protected final XmppConnectionService mXmppConnectionService;
PushManagementService(XmppConnectionService service) {
this.mXmppConnectionService = service;
}
PushManagementService(XmppConnectionService service) {
this.mXmppConnectionService = service;
}
void registerPushTokenOnServer(final Account account) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": has push support");
retrieveFcmInstanceToken(token -> {
final String androidId = PhoneHelper.getAndroidId(mXmppConnectionService);
final Jid appServer = Jid.of(mXmppConnectionService.getString(R.string.app_server));
IqPacket packet = mXmppConnectionService.getIqGenerator().pushTokenToAppServer(appServer, token, androidId);
mXmppConnectionService.sendIqPacket(account, packet, (a, p) -> {
Element command = p.findChild("command", "http://jabber.org/protocol/commands");
if (p.getType() == IqPacket.TYPE.RESULT && command != null) {
Element x = command.findChild("x", Namespace.DATA);
if (x != null) {
Data data = Data.parse(x);
try {
String node = data.getValue("node");
String secret = data.getValue("secret");
Jid jid = Jid.of(data.getValue("jid"));
if (node != null && secret != null) {
enablePushOnServer(a, jid, node, secret);
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
} else {
Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": invalid response from app server");
}
});
});
}
private static Data findResponseData(IqPacket response) {
final Element command = response.findChild("command", Namespace.COMMANDS);
final Element x = command == null ? null : command.findChild("x", Namespace.DATA);
return x == null ? null : Data.parse(x);
}
private void enablePushOnServer(final Account account, final Jid jid, final String node, final String secret) {
IqPacket enable = mXmppConnectionService.getIqGenerator().enablePush(jid, node, secret);
mXmppConnectionService.sendIqPacket(account, enable, (a, p) -> {
if (p.getType() == IqPacket.TYPE.RESULT) {
Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": successfully enabled push on server");
} else if (p.getType() == IqPacket.TYPE.ERROR) {
Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": enabling push on server failed");
}
});
}
private Jid getAppServer() {
return Jid.of(mXmppConnectionService.getString(R.string.app_server));
}
private void retrieveFcmInstanceToken(final OnGcmInstanceTokenRetrieved instanceTokenRetrieved) {
new Thread(() -> {
try {
instanceTokenRetrieved.onGcmInstanceTokenRetrieved(FirebaseInstanceId.getInstance().getToken());
} catch (Exception e) {
Log.d(Config.LOGTAG, "unable to get push token",e);
}
}).start();
void registerPushTokenOnServer(final Account account) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": has push support");
retrieveFcmInstanceToken(token -> {
final String androidId = PhoneHelper.getAndroidId(mXmppConnectionService);
final IqPacket packet = mXmppConnectionService.getIqGenerator().pushTokenToAppServer(getAppServer(), token, androidId);
mXmppConnectionService.sendIqPacket(account, packet, (a, response) -> {
final Data data = findResponseData(response);
if (response.getType() == IqPacket.TYPE.RESULT && data != null) {
try {
String node = data.getValue("node");
String secret = data.getValue("secret");
Jid jid = Jid.of(data.getValue("jid"));
if (node != null && secret != null) {
enablePushOnServer(a, jid, node, secret);
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
} else {
Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": invalid response from app server");
}
});
});
}
}
public void unregisterChannel(final Account account, final String channel) {
final String androidId = PhoneHelper.getAndroidId(mXmppConnectionService);
final Jid appServer = getAppServer();
final IqPacket packet = mXmppConnectionService.getIqGenerator().unregisterChannelOnAppServer(appServer, androidId, channel);
mXmppConnectionService.sendIqPacket(account, packet, (a, response) -> {
if (response.getType() == IqPacket.TYPE.RESULT) {
Log.d(Config.LOGTAG,a.getJid().asBareJid()+": successfully unregistered channel");
} else if (response.getType() == IqPacket.TYPE.ERROR) {
Log.d(Config.LOGTAG, a.getJid().asBareJid()+": unable to unregister channel with hash "+channel);
}
});
}
void registerPushTokenOnServer(final Conversation conversation) {
Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": room "+conversation.getJid().asBareJid()+" has push support");
retrieveFcmInstanceToken(token -> {
final Jid muc = conversation.getJid().asBareJid();
final String androidId = PhoneHelper.getAndroidId(mXmppConnectionService);
final IqPacket packet = mXmppConnectionService.getIqGenerator().pushTokenToAppServer(getAppServer(), token, androidId, muc);
packet.setTo(muc);
mXmppConnectionService.sendIqPacket(conversation.getAccount(), packet, (a, response) -> {
final Data data = findResponseData(response);
if (response.getType() == IqPacket.TYPE.RESULT && data != null) {
try {
final String node = data.getValue("node");
final String secret = data.getValue("secret");
final Jid jid = Jid.of(data.getValue("jid"));
if (node != null && secret != null) {
enablePushOnServer(conversation, jid, node, secret);
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
} else {
Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": invalid response from app server");
}
});
});
}
private void enablePushOnServer(final Account account, final Jid appServer, final String node, final String secret) {
final IqPacket enable = mXmppConnectionService.getIqGenerator().enablePush(appServer, node, secret);
mXmppConnectionService.sendIqPacket(account, enable, (a, p) -> {
if (p.getType() == IqPacket.TYPE.RESULT) {
Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": successfully enabled push on server");
} else if (p.getType() == IqPacket.TYPE.ERROR) {
Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": enabling push on server failed");
}
});
}
private void enablePushOnServer(final Conversation conversation, final Jid appServer, final String node, final String secret) {
final Jid muc = conversation.getJid().asBareJid();
final IqPacket enable = mXmppConnectionService.getIqGenerator().enablePush(appServer, node, secret);
enable.setTo(muc);
mXmppConnectionService.sendIqPacket(conversation.getAccount(), enable, (a, p) -> {
if (p.getType() == IqPacket.TYPE.RESULT) {
Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": successfully enabled push on " + muc);
if (conversation.setAttribute(Conversation.ATTRIBUTE_ALWAYS_NOTIFY, node)) {
mXmppConnectionService.updateConversation(conversation);
}
} else if (p.getType() == IqPacket.TYPE.ERROR) {
Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": enabling push on " + muc + " failed");
}
});
}
public void disablePushOnServer(final Conversation conversation) {
final Jid muc = conversation.getJid().asBareJid();
final String node = conversation.getAttribute(Conversation.ATTRIBUTE_PUSH_NODE);
if (node != null) {
final IqPacket disable = mXmppConnectionService.getIqGenerator().disablePush(getAppServer(), node);
disable.setTo(muc);
mXmppConnectionService.sendIqPacket(conversation.getAccount(), disable, (account, response) -> {
if (response.getType() == IqPacket.TYPE.ERROR) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": unable to disable push for room "+muc);
}
});
} else {
Log.d(Config.LOGTAG,conversation.getAccount().getJid().asBareJid()+": room "+muc+" has no stored node. unable to disable push");
}
}
private void retrieveFcmInstanceToken(final OnGcmInstanceTokenRetrieved instanceTokenRetrieved) {
FirebaseInstanceId.getInstance().getInstanceId().addOnCompleteListener(task -> {
if (!task.isSuccessful()) {
Log.d(Config.LOGTAG, "unable to get Firebase instance token", task.getException());
}
final InstanceIdResult result = task.getResult();
if (result != null) {
instanceTokenRetrieved.onGcmInstanceTokenRetrieved(result.getToken());
}
});
}
public boolean available(Account account) {
final XmppConnection connection = account.getXmppConnection();
return connection != null
&& connection.getFeatures().sm()
&& connection.getFeatures().push()
&& playServicesAvailable();
}
public boolean available(Account account) {
final XmppConnection connection = account.getXmppConnection();
return connection != null
&& connection.getFeatures().sm()
&& connection.getFeatures().push()
&& playServicesAvailable();
}
private boolean playServicesAvailable() {
return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(mXmppConnectionService) == ConnectionResult.SUCCESS;
}
private boolean playServicesAvailable() {
return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(mXmppConnectionService) == ConnectionResult.SUCCESS;
}
public boolean isStub() {
return false;
}
public boolean isStub() {
return false;
}
interface OnGcmInstanceTokenRetrieved {
void onGcmInstanceTokenRetrieved(String token);
}
interface OnGcmInstanceTokenRetrieved {
void onGcmInstanceTokenRetrieved(String token);
}
}

View File

@ -203,7 +203,7 @@ public class QuickConversationsService extends AbstractQuickConversationsService
os.close();
connection.connect();
final int code = connection.getResponseCode();
if (code == 200) {
if (code == 200 || code == 201) {
account.setOption(Account.OPTION_UNVERIFIED, false);
account.setOption(Account.OPTION_DISABLED, false);
awaitingAccountStateChange = new CountDownLatch(1);

View File

@ -42,6 +42,9 @@ public class ApiDialogHelper {
case 409:
res = R.string.logged_in_with_another_device;
break;
case 451:
res = R.string.not_available_in_your_country;
break;
case 500:
res = R.string.something_went_wrong_processing_your_request;
break;

View File

@ -6,4 +6,4 @@
<string name="pref_broadcast_last_activity_summary">إجعل كلّ جهات إتصالك تعلم أنك تستعمل كويكسي</string>
<string name="no_microphone_permission">كويكسي يحتاج الإتصال بالمايكروفون</string>
<string name="set_profile_picture">صورة حساب كويكسي</string>
</resources>
</resources>

View File

@ -19,4 +19,5 @@
<string name="no_microphone_permission">Quicksy се нуждае от достъп до микрофона</string>
<string name="foreground_service_channel_description">Тази категория известия се използва за показване на постоянно известие, което показва, че Quicksy работи.</string>
<string name="set_profile_picture">Профилна снимка за Quicksy</string>
<string name="not_available_in_your_country">Quicksy не може да се използва във Вашата страна.</string>
</resources>

View File

@ -19,4 +19,4 @@
<string name="no_microphone_permission">Quicksy necessita accés al micròfon</string>
<string name="foreground_service_channel_description">Aquest tipus de notificació s\'utilitza per mostrar una notificació permanent que indica que Quicksy s\'està executant.</string>
<string name="set_profile_picture">Imatge de perfil en Quicksy</string>
</resources>
</resources>

View File

@ -19,4 +19,5 @@
<string name="no_microphone_permission">Quicksy benötigt Zugriff auf das Mikrofon</string>
<string name="foreground_service_channel_description">Diese Benachrichtigungsart wird verwendet, um eine permanente Benachrichtigung anzuzeigen, die anzeigt, dass Quicksy gerade ausgeführt wird.</string>
<string name="set_profile_picture">Quicksy Profilbild</string>
<string name="not_available_in_your_country">Quicksy ist in deinem Land nicht verfügbar.</string>
</resources>

View File

@ -19,4 +19,4 @@
<string name="no_microphone_permission">Quicksy necesita acceder al micrófono</string>
<string name="foreground_service_channel_description">Esta categoría de notificación se usa para mostrar una notificación permantente indicando que Quicksy está ejecutándose.</string>
<string name="set_profile_picture">Foto de perfil en Quicksy</string>
</resources>
</resources>

View File

@ -19,4 +19,4 @@
<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="set_profile_picture">Photo de profil Quicksy</string>
</resources>
</resources>

View File

@ -19,4 +19,5 @@
<string name="no_microphone_permission">Quicksy precisa acceder ao micrófono</string>
<string name="foreground_service_channel_description">Esta categoría de notificacións utilízase para mostrar unha notificación permanente que indica que Quicksy está funcionando.</string>
<string name="set_profile_picture">Imaxe de perfil Quicksy</string>
<string name="not_available_in_your_country">Quicksy non está dispoñible no seu país.</string>
</resources>

View File

@ -19,4 +19,4 @@
<string name="no_microphone_permission">A Quicksy-nek hozzáférésre lenne szüksége a mikrofonhoz</string>
<string name="foreground_service_channel_description">Ez az értesítési kategória állandó értesítést jelenít meg arról, hogy a Quicksy fut.</string>
<string name="set_profile_picture">Quicksy profilkép</string>
</resources>
</resources>

View File

@ -19,4 +19,4 @@
<string name="no_microphone_permission">Quicksy ha bisogno di accedere al microfono</string>
<string name="foreground_service_channel_description">Questa categoria di notifiche è usata per mostrare una notifica permanente per indicare che Quicksy è in esecuzione.</string>
<string name="set_profile_picture">Immagine profilo di Quicksy</string>
</resources>
</resources>

View File

@ -19,4 +19,4 @@
<string name="no_microphone_permission">Quicksy はマイクにアクセスが必要です</string>
<string name="foreground_service_channel_description">この通知カテゴリーは Quicksy が実行されていることを表示する、永続的な通知を表示するために使用されます。</string>
<string name="set_profile_picture">Quicksy プロフィール写真</string>
</resources>
</resources>

View File

@ -19,4 +19,4 @@
<string name="no_microphone_permission">Quicksy heeft toegang nodig tot de microfoon</string>
<string name="foreground_service_channel_description">Deze meldingscategorie wordt gebruikt om een permanente melding weer te geven dat Quicksy wordt uitgevoerd.</string>
<string name="set_profile_picture">Quicksy-profielafbeelding</string>
</resources>
</resources>

View File

@ -19,4 +19,4 @@
<string name="no_microphone_permission">Quicksy potrzebuje dostępu do mikrofonu.</string>
<string name="foreground_service_channel_description">Ta kategoria powiadomień jest używana do wyświetlania ciągłego powiadomienia o tym, że Quicksy działa.</string>
<string name="set_profile_picture">Obrazek profilowy Quicksy</string>
</resources>
</resources>

View File

@ -19,4 +19,4 @@
<string name="no_microphone_permission">O Quicksy necessita de acesso ao microfone</string>
<string name="foreground_service_channel_description">Essa categoria de notificação é utilizada para exibir uma notificação permanente indicando que o Quicksy está em execução.</string>
<string name="set_profile_picture">Imagem de perfil do Quicksy</string>
</resources>
</resources>

View File

@ -21,4 +21,5 @@ sau chiar pierderea mesajelor.\nÎn continuare veți fi rugați să dezactivați
<string name="no_microphone_permission">Quicksy are nevoie de acces la microfon</string>
<string name="foreground_service_channel_description">Această categorie de notificări este folosită pentru a arăta o notificare permanentă ce indică rularea Quicksy</string>
<string name="set_profile_picture">Poză profil Quicksy</string>
<string name="not_available_in_your_country">Quicksy nu este disponibilă în țara dumneavoastră.</string>
</resources>

View File

@ -19,4 +19,4 @@
<string name="no_microphone_permission">Програма потребує доступу до мікрофона</string>
<string name="foreground_service_channel_description">Цей вид сповіщень показує постійне сповіщення про те, що ця програма працює.</string>
<string name="set_profile_picture">Зображення профілю для Quicksy</string>
</resources>
</resources>

View File

@ -19,4 +19,4 @@
<string name="no_microphone_permission">Quicksy需要麦克风权限</string>
<string name="foreground_service_channel_description">此通知类别用于显示表明Quicksy正在运行的永久通知。</string>
<string name="set_profile_picture">Quicksy个人资料图片</string>
</resources>
</resources>

View File

@ -19,4 +19,5 @@
<string name="no_microphone_permission">Quicksy needs access to the microphone</string>
<string name="foreground_service_channel_description">This notification category is used to display a permanent notification indicating that Quicksy is running.</string>
<string name="set_profile_picture">Quicksy profile picture</string>
<string name="not_available_in_your_country">Quicksy is not available in your country.</string>
</resources>