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 # Changelog
### Version 2.5.4
* stability improvements for group chats and channels
### Version 2.5.3 ### Version 2.5.3
* bug fixes for peer to peer file transfer (Jingle) * bug fixes for peer to peer file transfer (Jingle)
* fixed server info for unlimited/unknown max file size * fixed server info for unlimited/unknown max file size

View File

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

31
proguard-rules.pro vendored
View File

@ -20,3 +20,34 @@
-dontwarn com.google.firebase.analytics.connector.AnalyticsConnector -dontwarn com.google.firebase.analytics.connector.AnalyticsConnector
-dontwarn java.lang.** -dontwarn java.lang.**
-dontwarn javax.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; package eu.siacs.conversations.services;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
public class PushManagementService { public class PushManagementService {
@ -10,7 +11,19 @@ public class PushManagementService {
this.mXmppConnectionService = service; 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 //stub implementation. only affects playstore flavor
} }

View File

@ -2,7 +2,6 @@ package eu.siacs.conversations.crypto;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Intent; import android.content.Intent;
import android.os.Parcelable;
import android.support.annotation.StringRes; import android.support.annotation.StringRes;
import android.util.Log; import android.util.Log;
@ -94,7 +93,7 @@ public class PgpEngine {
break; break;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
callback.userInputRequried(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), message); callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), message);
break; break;
case OpenPgpApi.RESULT_CODE_ERROR: case OpenPgpApi.RESULT_CODE_ERROR:
OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR); OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
@ -133,7 +132,7 @@ public class PgpEngine {
callback.success(message); callback.success(message);
break; break;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
callback.userInputRequried(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), message); callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), message);
break; break;
case OpenPgpApi.RESULT_CODE_ERROR: case OpenPgpApi.RESULT_CODE_ERROR:
logError(conversation.getAccount(), result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); logError(conversation.getAccount(), result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
@ -200,7 +199,7 @@ public class PgpEngine {
callback.success(account); callback.success(account);
return; return;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
callback.userInputRequried(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), account); callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), account);
return; return;
case OpenPgpApi.RESULT_CODE_ERROR: case OpenPgpApi.RESULT_CODE_ERROR:
logError(account, result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); logError(account, result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
@ -249,7 +248,7 @@ public class PgpEngine {
callback.success(signatureBuilder.toString()); callback.success(signatureBuilder.toString());
return; return;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
callback.userInputRequried(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), status); callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), status);
return; return;
case OpenPgpApi.RESULT_CODE_ERROR: case OpenPgpApi.RESULT_CODE_ERROR:
OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR); OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
@ -276,7 +275,7 @@ public class PgpEngine {
callback.success(contact); callback.success(contact);
return; return;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
callback.userInputRequried(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), contact); callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), contact);
return; return;
case OpenPgpApi.RESULT_CODE_ERROR: case OpenPgpApi.RESULT_CODE_ERROR:
logError(contact.getAccount(), result.getParcelableExtra(OpenPgpApi.RESULT_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; protected final JSONObject keys;
private final Roster roster = new Roster(this); private final Roster roster = new Roster(this);
private final Collection<Jid> blocklist = new CopyOnWriteArraySet<>(); private final Collection<Jid> blocklist = new CopyOnWriteArraySet<>();
public List<Conversation> pendingConferenceJoins = new CopyOnWriteArrayList<>(); public final Set<Conversation> pendingConferenceJoins = new HashSet<>();
public List<Conversation> pendingConferenceLeaves = new CopyOnWriteArrayList<>(); 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 Jid jid;
protected String password; protected String password;
protected int options = 0; 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_MUTED_TILL = "muted_till";
public static final String ATTRIBUTE_ALWAYS_NOTIFY = "always_notify"; 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"; public static final String ATTRIBUTE_LAST_CLEAR_HISTORY = "last_clear_history";
static final String ATTRIBUTE_MUC_PASSWORD = "muc_password"; static final String ATTRIBUTE_MUC_PASSWORD = "muc_password";
private static final String ATTRIBUTE_NEXT_MESSAGE = "next_message"; 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.services.MessageArchiveService;
import eu.siacs.conversations.utils.JidHelper; import eu.siacs.conversations.utils.JidHelper;
import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.chatstate.ChatState; import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.forms.Data;
import eu.siacs.conversations.xmpp.forms.Field; import eu.siacs.conversations.xmpp.forms.Field;
@ -113,6 +114,10 @@ public class MucOptions {
return MessageArchiveService.Version.has(getFeatures()); return MessageArchiveService.Version.has(getFeatures());
} }
public boolean push() {
return getFeatures().contains(Namespace.PUSH);
}
public boolean updateConfiguration(ServiceDiscoveryResult serviceDiscoveryResult) { public boolean updateConfiguration(ServiceDiscoveryResult serviceDiscoveryResult) {
this.serviceDiscoveryResult = serviceDiscoveryResult; this.serviceDiscoveryResult = serviceDiscoveryResult;
String name; 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) { public IqPacket generateSetBlockRequest(final Jid jid, boolean reportSpam) {
final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
final Element block = iq.addChild("block", Namespace.BLOCKING); 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) { if (reportSpam) {
item.addChild("report", "urn:xmpp:reporting:0").addChild("spam"); item.addChild("report", "urn:xmpp:reporting:0").addChild("spam");
} }
@ -316,7 +316,7 @@ public class IqGenerator extends AbstractGenerator {
public IqPacket generateSetUnblockRequest(final Jid jid) { public IqPacket generateSetUnblockRequest(final Jid jid) {
final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
final Element block = iq.addChild("unblock", Namespace.BLOCKING); 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; return iq;
} }
@ -423,29 +423,60 @@ public class IqGenerator extends AbstractGenerator {
} }
public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId) { 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); 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("node", "register-push-fcm");
command.setAttribute("action", "execute"); command.setAttribute("action", "execute");
Data data = new Data(); final Data data = new Data();
data.put("token", token); data.put("token", token);
data.put("android-id", deviceId); 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(); data.submit();
command.addChild(data); command.addChild(data);
return packet; 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); 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("jid", jid.toString());
enable.setAttribute("node", node); enable.setAttribute("node", node);
if (secret != null) {
Data data = new Data(); Data data = new Data();
data.setFormType(Namespace.PUBSUB_PUBLISH_OPTIONS); data.setFormType(Namespace.PUBSUB_PUBLISH_OPTIONS);
data.put("secret", secret); data.put("secret", secret);
data.submit(); data.submit();
enable.addChild(data); 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; return packet;
} }

View File

@ -26,6 +26,7 @@ import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Element;
@ -382,6 +383,29 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
response = mXmppConnectionService.getIqGenerator().entityTimeResponse(packet); response = mXmppConnectionService.getIqGenerator().entityTimeResponse(packet);
} }
mXmppConnectionService.sendIqPacket(account, response, null); 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 { } else {
if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) { if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) {
final IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR); final IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR);

View File

@ -22,6 +22,7 @@ import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Conversational;
import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.ReadByMarker; import eu.siacs.conversations.entities.ReadByMarker;
@ -126,7 +127,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
service.reportBrokenSessionException(e, postpone); service.reportBrokenSessionException(e, postpone);
return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status); return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
} else { } 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; return null;
} }
} catch (NotEncryptedForThisDeviceException e) { } catch (NotEncryptedForThisDeviceException e) {
@ -264,6 +266,17 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
packet.getId(), packet.getId(),
Message.STATUS_SEND_FAILED, Message.STATUS_SEND_FAILED,
extractErrorMessage(packet)); 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; return true;
} }
@ -437,6 +450,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
origin = from; origin = from;
} }
//TODO either or is probably fine?
final boolean checkedForDuplicates = serverMsgId != null && remoteMsgId != null && !conversation.possibleDuplicate(serverMsgId, remoteMsgId); final boolean checkedForDuplicates = serverMsgId != null && remoteMsgId != null && !conversation.possibleDuplicate(serverMsgId, remoteMsgId);
if (origin != null) { if (origin != null) {
@ -598,7 +612,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
} else { } else {
serverMsgIdUpdated = false; 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; 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) { if (stream != null) {
try { try {
stream.close(); 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) { if (socket != null) {
try { try {
socket.close(); 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.ListItem;
import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.RawBlockable;
import eu.siacs.conversations.http.services.MuclumbusService; import eu.siacs.conversations.http.services.MuclumbusService;
import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
@ -272,7 +273,9 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
} }
public Bitmap get(ListItem item, int size, boolean cachedOnly) { 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); return get((Contact) item, size, cachedOnly);
} else if (item instanceof Bookmark) { } else if (item instanceof Bookmark) {
Bookmark bookmark = (Bookmark) item; Bookmark bookmark = (Bookmark) item;

View File

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

View File

@ -28,14 +28,18 @@ public final class BlockContactDialog {
final String value; final String value;
@StringRes int res; @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); builder.setTitle(isBlocked ? R.string.action_unblock_domain : R.string.action_block_domain);
value = Jid.ofDomain(blockable.getJid().getDomain()).toString(); value = Jid.ofDomain(blockable.getJid().getDomain()).toString();
res = isBlocked ? R.string.unblock_domain_text : R.string.block_domain_text; res = isBlocked ? R.string.unblock_domain_text : R.string.block_domain_text;
} else { } else {
int resBlockAction = blockable instanceof Conversation && ((Conversation) blockable).isWithStranger() ? R.string.block_stranger : R.string.action_block_contact; 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); 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; res = isBlocked ? R.string.unblock_contact_text : R.string.block_contact_text;
} }
binding.text.setText(JidDialog.style(xmppActivity, res, value)); 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.R;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Blockable;
import eu.siacs.conversations.entities.Contact; 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.ui.interfaces.OnBackendConnected;
import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
import rocks.xmpp.addr.Jid; import rocks.xmpp.addr.Jid;
@ -23,7 +26,7 @@ public class BlocklistActivity extends AbstractSearchableListItemActivity implem
public void onCreate(final Bundle savedInstanceState) { public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
getListView().setOnItemLongClickListener((parent, view, position, id) -> { getListView().setOnItemLongClickListener((parent, view, position, id) -> {
BlockContactDialog.show(BlocklistActivity.this, (Contact) getListItems().get(position)); BlockContactDialog.show(BlocklistActivity.this, (Blockable) getListItems().get(position));
return true; return true;
}); });
this.binding.fab.show(); this.binding.fab.show();
@ -50,9 +53,14 @@ public class BlocklistActivity extends AbstractSearchableListItemActivity implem
getListItems().clear(); getListItems().clear();
if (account != null) { if (account != null) {
for (final Jid jid : account.getBlocklist()) { for (final Jid jid : account.getBlocklist()) {
final Contact contact = account.getRoster().getContact(jid); ListItem item;
if (contact.match(this, needle) && contact.isBlocked()) { if (jid.isFullJid()) {
getListItems().add(contact); item = new RawBlockable(account, jid);
} else {
item = account.getRoster().getContact(jid);
}
if (item.match(this, needle)) {
getListItems().add(item);
} }
} }
Collections.sort(getListItems()); Collections.sort(getListItems());
@ -78,8 +86,8 @@ public class BlocklistActivity extends AbstractSearchableListItemActivity implem
); );
dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid) -> { dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid) -> {
Contact contact = account.getRoster().getContact(contactJid); Blockable blockable = new RawBlockable(account, contactJid);
if (xmppConnectionService.sendBlockRequest(contact, false)) { if (xmppConnectionService.sendBlockRequest(blockable, false)) {
Toast.makeText(BlocklistActivity.this, R.string.corresponding_conversations_closed, Toast.LENGTH_SHORT).show(); Toast.makeText(BlocklistActivity.this, R.string.corresponding_conversations_closed, Toast.LENGTH_SHORT).show();
} }
return true; return true;
@ -101,4 +109,5 @@ public class BlocklistActivity extends AbstractSearchableListItemActivity implem
public void OnUpdateBlocklist(final OnUpdateBlocklist.Status status) { public void OnUpdateBlocklist(final OnUpdateBlocklist.Status status) {
refreshUi(); refreshUi();
} }
} }

View File

@ -83,7 +83,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
} }
@Override @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 @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 @Override
public void userInputRequried(PendingIntent pi, Message message) { public void userInputRequired(PendingIntent pi, Message message) {
hidePrepareFileToast(prepareFileToast); hidePrepareFileToast(prepareFileToast);
} }
}); });
@ -688,7 +688,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
new UiCallback<Message>() { new UiCallback<Message>() {
@Override @Override
public void userInputRequried(PendingIntent pi, Message object) { public void userInputRequired(PendingIntent pi, Message object) {
hidePrepareFileToast(prepareFileToast); hidePrepareFileToast(prepareFileToast);
} }
@ -1326,7 +1326,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
new UiCallback<Contact>() { new UiCallback<Contact>() {
@Override @Override
public void userInputRequried(PendingIntent pi, Contact contact) { public void userInputRequired(PendingIntent pi, Contact contact) {
startPendingIntent(pi, attachmentChoice); startPendingIntent(pi, attachmentChoice);
} }
@ -2284,7 +2284,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
status = Presence.Status.OFFLINE; status = Presence.Status.OFFLINE;
} }
this.binding.textSendButton.setTag(action); 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() { protected void updateStatusMessages() {
@ -2456,7 +2459,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
new UiCallback<Contact>() { new UiCallback<Contact>() {
@Override @Override
public void userInputRequried(PendingIntent pi, Contact contact) { public void userInputRequired(PendingIntent pi, Contact contact) {
startPendingIntent(pi, REQUEST_ENCRYPT_MESSAGE); startPendingIntent(pi, REQUEST_ENCRYPT_MESSAGE);
} }
@ -2512,7 +2515,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
new UiCallback<Message>() { new UiCallback<Message>() {
@Override @Override
public void userInputRequried(PendingIntent pi, Message message) { public void userInputRequired(PendingIntent pi, Message message) {
startPendingIntent(pi, REQUEST_SEND_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>() { private final UiCallback<Avatar> mAvatarFetchCallback = new UiCallback<Avatar>() {
@Override @Override
public void userInputRequried(final PendingIntent pi, final Avatar avatar) { public void userInputRequired(final PendingIntent pi, final Avatar avatar) {
finishInitialSetup(avatar); finishInitialSetup(avatar);
} }
@ -917,7 +917,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
} }
@Override @Override
public void userInputRequried(PendingIntent pi, String object) { public void userInputRequired(PendingIntent pi, String object) {
mPendingPresenceTemplate.push(template); mPendingPresenceTemplate.push(template);
try { try {
startIntentSenderForResult(pi.getIntentSender(), REQUEST_CHANGE_STATUS, null, 0, 0, 0); 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.api.IMapController;
import org.osmdroid.config.Configuration; import org.osmdroid.config.Configuration;
import org.osmdroid.config.IConfigurationProvider; import org.osmdroid.config.IConfigurationProvider;
import org.osmdroid.tileprovider.tilesource.TileSourceFactory;
import org.osmdroid.tileprovider.tilesource.XYTileSource; import org.osmdroid.tileprovider.tilesource.XYTileSource;
import org.osmdroid.util.GeoPoint; import org.osmdroid.util.GeoPoint;
import org.osmdroid.views.CustomZoomButtonsController;
import org.osmdroid.views.MapView; import org.osmdroid.views.MapView;
import org.osmdroid.views.overlay.Overlay; import org.osmdroid.views.overlay.Overlay;
@ -73,14 +75,6 @@ public abstract class LocationActivity extends ActionBarActivity implements Loca
clearMarkers(); 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 @Override
protected void onCreate(final Bundle savedInstanceState) { protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -103,7 +97,7 @@ public abstract class LocationActivity extends ActionBarActivity implements Loca
final IConfigurationProvider config = Configuration.getInstance(); final IConfigurationProvider config = Configuration.getInstance();
config.load(ctx, getPreferences()); 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)) { if (QuickConversationsService.isConversations() && getBooleanPreference("use_tor", R.bool.use_tor)) {
try { try {
config.setHttpProxy(HttpConnectionManager.getProxy()); config.setHttpProxy(HttpConnectionManager.getProxy());
@ -111,17 +105,6 @@ public abstract class LocationActivity extends ActionBarActivity implements Loca
throw new RuntimeException("Unable to configure proxy"); 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 @Override
@ -150,8 +133,8 @@ public abstract class LocationActivity extends ActionBarActivity implements Loca
protected void setupMapView(MapView mapView, final GeoPoint pos) { protected void setupMapView(MapView mapView, final GeoPoint pos) {
map = mapView; map = mapView;
map.setTileSource(tileSource()); map.setTileSource(TileSourceFactory.MAPNIK);
map.setBuiltInZoomControls(false); map.getZoomController().setVisibility(CustomZoomButtonsController.Visibility.NEVER);
map.setMultiTouchControls(true); map.setMultiTouchControls(true);
map.setTilesScaledToDpi(true); map.setTilesScaledToDpi(true);
mapController = map.getController(); mapController = map.getController();
@ -251,7 +234,7 @@ public abstract class LocationActivity extends ActionBarActivity implements Loca
requestLocationUpdates(); requestLocationUpdates();
updateLocationMarkers(); updateLocationMarkers();
updateUi(); updateUi();
map.setTileSource(tileSource()); map.setTileSource(TileSourceFactory.MAPNIK);
map.setTilesScaledToDpi(true); map.setTilesScaledToDpi(true);
if (mapAtInitialLoc()) { if (mapAtInitialLoc()) {

View File

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

View File

@ -246,8 +246,12 @@ public final class CryptoHelper {
return prettifyFingerprintCert(bytesToHex(fingerprint)); 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) { 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) { public static String getFingerprint(String value) {

View File

@ -7,7 +7,7 @@ import org.hsluv.HUSLColorConverter;
import java.security.MessageDigest; import java.security.MessageDigest;
public class XEP0392Helper { class XEP0392Helper {
private static double angle(String nickname) { private static double angle(String nickname) {
try { try {
@ -20,7 +20,7 @@ public class XEP0392Helper {
} }
} }
public static int rgbFromNick(String name) { static int rgbFromNick(String name) {
double[] hsluv = new double[3]; double[] hsluv = new double[3];
hsluv[0] = angle(name) * 360; hsluv[0] = angle(name) * 360;
hsluv[1] = 100; 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_S5B = "urn:xmpp:jingle:transports:s5b:1";
public static final String JINGLE_TRANSPORTS_IBB = "urn:xmpp:jingle:transports:ibb: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 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.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
public class XmlReader { public class XmlReader implements Closeable {
private XmlPullParser parser; private final XmlPullParser parser;
private InputStream is; private InputStream is;
public XmlReader() { public XmlReader() {
@ -48,6 +49,11 @@ public class XmlReader {
} }
} }
@Override
public void close() {
this.is = null;
}
public Tag readTag() throws IOException { public Tag readTag() throws IOException {
try { try {
while (this.is != null && parser.next() != XmlPullParser.END_DOCUMENT) { while (this.is != null && parser.next() != XmlPullParser.END_DOCUMENT) {

View File

@ -1439,15 +1439,8 @@ public class XmppConnection implements Runnable {
} }
private void forceCloseSocket() { private void forceCloseSocket() {
if (socket != null) { FileBackend.close(this.socket);
try { FileBackend.close(this.tagReader);
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");
}
} }
public void interrupt() { public void interrupt() {
@ -1458,7 +1451,7 @@ public class XmppConnection implements Runnable {
public void disconnect(final boolean force) { public void disconnect(final boolean force) {
interrupt(); 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) { if (force) {
forceCloseSocket(); forceCloseSocket();
} else { } else {
@ -1798,8 +1791,8 @@ public class XmppConnection implements Runnable {
} }
public boolean push() { public boolean push() {
return hasDiscoFeature(account.getJid().asBareJid(), "urn:xmpp:push:0") return hasDiscoFeature(account.getJid().asBareJid(), Namespace.PUSH)
|| hasDiscoFeature(Jid.of(account.getServer()), "urn:xmpp:push:0"); || hasDiscoFeature(Jid.of(account.getServer()), Namespace.PUSH);
} }
public boolean rosterVersioning() { public boolean rosterVersioning() {

View File

@ -776,4 +776,10 @@
<string name="open_with">Отваряне с…</string> <string name="open_with">Отваряне с…</string>
<string name="set_profile_picture">Профилна снимка за Conversations</string> <string name="set_profile_picture">Профилна снимка за Conversations</string>
<string name="choose_account">Изберете профил</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> </resources>

View File

@ -17,6 +17,8 @@
<string name="action_unblock_contact">Kontakt entsperren</string> <string name="action_unblock_contact">Kontakt entsperren</string>
<string name="action_block_domain">Domain sperren</string> <string name="action_block_domain">Domain sperren</string>
<string name="action_unblock_domain">Domain entsperren</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_manage_accounts">Konten verwalten</string>
<string name="title_activity_settings">Einstellungen</string> <string name="title_activity_settings">Einstellungen</string>
<string name="title_activity_sharewith">Mit Unterhaltung teilen</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_unblock_contact">Desbloquear contacto</string>
<string name="action_block_domain">Bloquear dominio</string> <string name="action_block_domain">Bloquear dominio</string>
<string name="action_unblock_domain">Desbloquear 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_manage_accounts">Xestionar contas</string>
<string name="title_activity_settings">Axustes</string> <string name="title_activity_settings">Axustes</string>
<string name="title_activity_sharewith">Compartir na conversa</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_unblock_contact">Deblochează contact</string>
<string name="action_block_domain">Blochează domeniu</string> <string name="action_block_domain">Blochează domeniu</string>
<string name="action_unblock_domain">Deblochează 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_manage_accounts">Configurează conturile</string>
<string name="title_activity_settings">Setări</string> <string name="title_activity_settings">Setări</string>
<string name="title_activity_sharewith">Partajează într-o conversație</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_settings">Настройки</string>
<string name="action_add">Новая беседа</string> <string name="action_add">Новая беседа</string>
<string name="action_accounts">Управление аккаунтами</string> <string name="action_accounts">Управление аккаунтами</string>
<string name="action_end_conversation">Закрыть текущую беседу</string>
<string name="action_contact_details">Сведения о контакте</string> <string name="action_contact_details">Сведения о контакте</string>
<string name="action_muc_details">Подробности конференции</string> <string name="action_muc_details">Подробности конференции</string>
<string name="channel_details">Сведения о канале</string>
<string name="action_secure">Защищённая беседа</string> <string name="action_secure">Защищённая беседа</string>
<string name="action_add_account">Добавить аккаунт</string> <string name="action_add_account">Добавить аккаунт</string>
<string name="action_edit_contact">Редактировать контакт</string> <string name="action_edit_contact">Редактировать контакт</string>
@ -48,6 +50,7 @@
<string name="share_with">Поделиться с</string> <string name="share_with">Поделиться с</string>
<string name="start_conversation">Начать беседу</string> <string name="start_conversation">Начать беседу</string>
<string name="invite_contact">Пригласить собеседника</string> <string name="invite_contact">Пригласить собеседника</string>
<string name="invite">Пригласить</string>
<string name="contacts">Контакты</string> <string name="contacts">Контакты</string>
<string name="contact">Контакт</string> <string name="contact">Контакт</string>
<string name="cancel">Отмена</string> <string name="cancel">Отмена</string>
@ -78,6 +81,7 @@
<string name="clear_histor_msg">Вы хотите удалить все сообщения в этой беседе?\n\n<b>Предупреждение:</b> Данная операция не повлияет на сообщения, хранящиеся на других устройствах или серверах.</string> <string name="clear_histor_msg">Вы хотите удалить все сообщения в этой беседе?\n\n<b>Предупреждение:</b> Данная операция не повлияет на сообщения, хранящиеся на других устройствах или серверах.</string>
<string name="delete_file_dialog">Удалить файл</string> <string name="delete_file_dialog">Удалить файл</string>
<string name="delete_file_dialog_msg">Вы уверены, что хотите удалить этот файл?\n\n<b>Предупреждение:</b> Данная операция не удалит копии этого файла, хранящиеся на других устройствах или серверах.</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="choose_presence">Выберите устройство</string>
<string name="send_unencrypted_message">Нешифрованное сообщение</string> <string name="send_unencrypted_message">Нешифрованное сообщение</string>
<string name="send_message">Сообщение</string> <string name="send_message">Сообщение</string>
@ -158,15 +162,18 @@
<string name="mgmt_account_disable">Временно отключить</string> <string name="mgmt_account_disable">Временно отключить</string>
<string name="mgmt_account_publish_avatar">Разместить аватар</string> <string name="mgmt_account_publish_avatar">Разместить аватар</string>
<string name="mgmt_account_publish_pgp">Анонсировать OpenPGP ключ</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="unpublish_pgp_message">Вы действительно хотите удалить ваш OpenPGP публичный ключ из опубликованных?\nВаши собеседники не смогут больше отправлять вам зашифрованные OpenPGP сообщения.</string>
<string name="openpgp_has_been_published">Открытый ключ OpenPGP был опубликован.</string> <string name="openpgp_has_been_published">Открытый ключ OpenPGP был опубликован.</string>
<string name="mgmt_account_enable">Включить аккаунт</string> <string name="mgmt_account_enable">Включить аккаунт</string>
<string name="mgmt_account_are_you_sure">Вы уверены?</string> <string name="mgmt_account_are_you_sure">Вы уверены?</string>
<string name="mgmt_account_delete_confirm_text">Если вы удалите аккаунт, будет потеряна вся история переписки.</string> <string name="mgmt_account_delete_confirm_text">Если вы удалите аккаунт, будет потеряна вся история переписки.</string>
<string name="attach_record_voice">Запись голоса</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="account_settings_example_jabber_id">username@example.com</string>
<string name="password">Пароль</string> <string name="password">Пароль</string>
<string name="invalid_jid">Недопустимый XMPP-адрес</string>
<string name="error_out_of_memory">Недостаточно памяти. Изображение слишком большое</string> <string name="error_out_of_memory">Недостаточно памяти. Изображение слишком большое</string>
<string name="add_phone_book_text">Вы хотите добавить %s в вашу адресную книгу?</string> <string name="add_phone_book_text">Вы хотите добавить %s в вашу адресную книгу?</string>
<string name="server_info_show_more">Информация о сервере</string> <string name="server_info_show_more">Информация о сервере</string>
@ -201,6 +208,7 @@
<string name="fetching_keys">Получение ключей…</string> <string name="fetching_keys">Получение ключей…</string>
<string name="done">Готово</string> <string name="done">Готово</string>
<string name="decrypt">Расшифровать</string> <string name="decrypt">Расшифровать</string>
<string name="bookmarks">Закладки</string>
<string name="search">Поиск</string> <string name="search">Поиск</string>
<string name="enter_contact">Добавить контакт</string> <string name="enter_contact">Добавить контакт</string>
<string name="delete_contact">Удалить контакт</string> <string name="delete_contact">Удалить контакт</string>
@ -213,6 +221,8 @@
<string name="join">Присоединиться</string> <string name="join">Присоединиться</string>
<string name="save_as_bookmark">Сохранить закладку</string> <string name="save_as_bookmark">Сохранить закладку</string>
<string name="delete_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="bookmark_already_exists">Такая закладка уже существует</string>
<string name="action_edit_subject">Редактировать тему конференции</string> <string name="action_edit_subject">Редактировать тему конференции</string>
<string name="topic">Тема</string> <string name="topic">Тема</string>
@ -253,6 +263,7 @@
<string name="pref_allow_message_correction_summary">Позволить контактам редактировать сообщения</string> <string name="pref_allow_message_correction_summary">Позволить контактам редактировать сообщения</string>
<string name="pref_expert_options">Расширенные настройки</string> <string name="pref_expert_options">Расширенные настройки</string>
<string name="pref_expert_options_summary">Пожалуйста, будьте осторожны с данными настройками</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">Тихие часы</string>
<string name="title_pref_quiet_hours_start_time">Начало</string> <string name="title_pref_quiet_hours_start_time">Начало</string>
<string name="title_pref_quiet_hours_end_time">Окончание</string> <string name="title_pref_quiet_hours_end_time">Окончание</string>
@ -281,6 +292,9 @@
<string name="send_again">Отправить ещё раз</string> <string name="send_again">Отправить ещё раз</string>
<string name="file_url">URL файла</string> <string name="file_url">URL файла</string>
<string name="url_copied_to_clipboard">Ссылка скопирована в буфер обмена</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="scan_qr_code">Сканировать 2D штрихкод</string>
<string name="show_qr_code">Показать 2D штрихкод</string> <string name="show_qr_code">Показать 2D штрихкод</string>
<string name="show_block_list">Показать чёрный список</string> <string name="show_block_list">Показать чёрный список</string>
@ -289,6 +303,14 @@
<string name="try_again">Повторить</string> <string name="try_again">Повторить</string>
<string name="pref_keep_foreground_service">Оставить службу на переднем плане</string> <string name="pref_keep_foreground_service">Оставить службу на переднем плане</string>
<string name="pref_keep_foreground_service_summary">Не позволяет операционной системе закрыть ваше соединение</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="choose_file">Выбрать файл</string>
<string name="receiving_x_file">%1$s загружается (%2$d%% выполнено)</string> <string name="receiving_x_file">%1$s загружается (%2$d%% выполнено)</string>
<string name="download_x_file">Загрузить %s</string> <string name="download_x_file">Загрузить %s</string>
@ -311,7 +333,7 @@
<string name="conference_creation_failed">Не удалось создать конференцию!</string> <string name="conference_creation_failed">Не удалось создать конференцию!</string>
<string name="account_image_description">Аватар аккаунта</string> <string name="account_image_description">Аватар аккаунта</string>
<string name="copy_omemo_clipboard_description">Скопировать OMEMO-отпечаток в буфер обмена</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">Очистить устройства</string>
<string name="clear_other_devices_desc">Вы уверены, что хотите очистить все остальные устройства из анонса ключей OMEMO? При соединении устройств в следующий раз новые ключи анонсируются автоматически, но устройства могут не получить сообщения, посланные до этого.</string> <string name="clear_other_devices_desc">Вы уверены, что хотите очистить все остальные устройства из анонса ключей OMEMO? При соединении устройств в следующий раз новые ключи анонсируются автоматически, но устройства могут не получить сообщения, посланные до этого.</string>
<string name="error_no_keys_to_trust_server_error">Для этого контакта не существует доступных ключей.\nПопытка получения новых ключей с сервера оказалась неудачной. Возможно, что-то не так с сервером вашего собеседника.</string> <string name="error_no_keys_to_trust_server_error">Для этого контакта не существует доступных ключей.\nПопытка получения новых ключей с сервера оказалась неудачной. Возможно, что-то не так с сервером вашего собеседника.</string>
@ -366,8 +388,8 @@
<string name="hide_offline">Скрыть пользователей вне сети</string> <string name="hide_offline">Скрыть пользователей вне сети</string>
<string name="contact_is_typing">%s печатает…</string> <string name="contact_is_typing">%s печатает…</string>
<string name="contact_has_stopped_typing">%s прекратил набор</string> <string name="contact_has_stopped_typing">%s прекратил набор</string>
<string name="contacts_are_typing">%s набирает...</string> <string name="contacts_are_typing">%s печатают...</string>
<string name="contacts_have_stopped_typing">%s перестал печатать</string> <string name="contacts_have_stopped_typing">%s перестали печатать</string>
<string name="pref_chat_states">Оповещения о наборе</string> <string name="pref_chat_states">Оповещения о наборе</string>
<string name="pref_chat_states_summary">Позволяет вашим контактам видеть когда вы пишете им новое сообщение</string> <string name="pref_chat_states_summary">Позволяет вашим контактам видеть когда вы пишете им новое сообщение</string>
<string name="send_location">Отправить местоположение</string> <string name="send_location">Отправить местоположение</string>
@ -395,7 +417,8 @@
<string name="recently_used">Последнее выбранное</string> <string name="recently_used">Последнее выбранное</string>
<string name="choose_quick_action">Выбрать быстрое действие</string> <string name="choose_quick_action">Выбрать быстрое действие</string>
<string name="search_contacts">Поиск контактов</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="user_has_left_conference">%1$s покинул конференцию!</string>
<string name="username">Имя пользователя</string> <string name="username">Имя пользователя</string>
<string name="username_hint">Имя пользователя</string> <string name="username_hint">Имя пользователя</string>
@ -409,8 +432,8 @@
<string name="account_status_host_unknown">Сервер не ответственен за домен</string> <string name="account_status_host_unknown">Сервер не ответственен за домен</string>
<string name="server_info_broken">Повреждено</string> <string name="server_info_broken">Повреждено</string>
<string name="pref_presence_settings">Доступность</string> <string name="pref_presence_settings">Доступность</string>
<string name="pref_away_when_screen_off">Вышел когда экран выключен</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_summary">Отмечает ваш ресурс как «отошёл» когда экран выключен</string>
<string name="pref_dnd_on_silent_mode">\"Не беспокоить\" в беззвучном режиме</string> <string name="pref_dnd_on_silent_mode">\"Не беспокоить\" в беззвучном режиме</string>
<string name="pref_dnd_on_silent_mode_summary">Помечать ресурс как \"Не беспокоить\", когда устройство в беззвучном режиме</string> <string name="pref_dnd_on_silent_mode_summary">Помечать ресурс как \"Не беспокоить\", когда устройство в беззвучном режиме</string>
<string name="pref_treat_vibrate_as_silent">Не доступен в режиме вибрации</string> <string name="pref_treat_vibrate_as_silent">Не доступен в режиме вибрации</string>
@ -430,7 +453,7 @@
<string name="certificate_chain_is_not_trusted">Цепочка сертификата не доверена</string> <string name="certificate_chain_is_not_trusted">Цепочка сертификата не доверена</string>
<string name="action_renew_certificate">Обновить сертификат</string> <string name="action_renew_certificate">Обновить сертификат</string>
<string name="error_fetching_omemo_key">Ошибка при получении OMEMO ключа!</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="device_does_not_support_certificates">Ваше устройство не поддерживает выбор клиентских сертификатов!</string>
<string name="pref_connection_options">Подключение</string> <string name="pref_connection_options">Подключение</string>
<string name="pref_use_tor">Соединение через Tor</string> <string name="pref_use_tor">Соединение через Tor</string>
@ -459,6 +482,8 @@
<string name="notify_only_when_highlighted">Уведомлять только при упоминании</string> <string name="notify_only_when_highlighted">Уведомлять только при упоминании</string>
<string name="notify_never">Без уведомления</string> <string name="notify_never">Без уведомления</string>
<string name="notify_paused">Уведомления приостановлены</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="always">Всегда</string>
<string name="automatically">Автоматически</string> <string name="automatically">Автоматически</string>
<string name="battery_optimizations_enabled">Оптимизации энергопотребления разрешены</string> <string name="battery_optimizations_enabled">Оптимизации энергопотребления разрешены</string>
@ -475,9 +500,12 @@
<string name="security_error_invalid_file_access">Ошибка безопасности: недействительный доступ к файлу</string> <string name="security_error_invalid_file_access">Ошибка безопасности: недействительный доступ к файлу</string>
<string name="no_application_to_share_uri">Не найдено приложения для отправки</string> <string name="no_application_to_share_uri">Не найдено приложения для отправки</string>
<string name="share_uri_with">Отправить 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="create_account">Создать аккаунт</string>
<string name="use_own_provider">Использовать свой провайдер</string> <string name="use_own_provider">Использовать свой провайдер</string>
<string name="pick_your_username">Выберите имя пользователя</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="status_message">Статусное собщение</string>
<string name="presence_chat">Свободен для общения</string> <string name="presence_chat">Свободен для общения</string>
<string name="presence_online">В сети</string> <string name="presence_online">В сети</string>
@ -491,6 +519,7 @@
<string name="choose_participants">Выбрать участников</string> <string name="choose_participants">Выбрать участников</string>
<string name="creating_conference">Создание конференции…</string> <string name="creating_conference">Создание конференции…</string>
<string name="invite_again">Пригласить ещё раз</string> <string name="invite_again">Пригласить ещё раз</string>
<string name="gp_disable">Выключен</string>
<string name="gp_short">Короткий</string> <string name="gp_short">Короткий</string>
<string name="gp_medium">Средний</string> <string name="gp_medium">Средний</string>
<string name="gp_long">Длинный</string> <string name="gp_long">Длинный</string>
@ -506,7 +535,7 @@
<string name="type_pc">Компьютер</string> <string name="type_pc">Компьютер</string>
<string name="type_phone">Телефон</string> <string name="type_phone">Телефон</string>
<string name="type_tablet">Планшет</string> <string name="type_tablet">Планшет</string>
<string name="type_web">Веб браузер</string> <string name="type_web">Веб-браузер</string>
<string name="type_console">Консоль</string> <string name="type_console">Консоль</string>
<string name="payment_required">Требуется оплата</string> <string name="payment_required">Требуется оплата</string>
<string name="missing_internet_permission">Доступ в интернет запрещён</string> <string name="missing_internet_permission">Доступ в интернет запрещён</string>
@ -548,10 +577,10 @@
<string name="pref_clean_private_storage_summary">Очистить закрытое хранилище, где хранятся файлы (Файлы можно заново скачать с сервера)</string> <string name="pref_clean_private_storage_summary">Очистить закрытое хранилище, где хранятся файлы (Файлы можно заново скачать с сервера)</string>
<string name="i_followed_this_link_from_a_trusted_source">Открывать ссылки из надёжного источника</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="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="show_inactive_devices">Показывать неактивные</string>
<string name="hide_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> <string name="distrust_omemo_key_text">Вы действительно хотите удалить устройство из доверенных?\Устройство и сообщения, полученные с этого устройства, будут помечаться как недоверенные.</string>
<plurals name="seconds"> <plurals name="seconds">
<item quantity="one">%d секунда</item> <item quantity="one">%d секунда</item>
@ -618,7 +647,10 @@
<string name="copy_to_clipboard">Скопировать в буфер обмена</string> <string name="copy_to_clipboard">Скопировать в буфер обмена</string>
<string name="message_copied_to_clipboard">Сообщение скопировано в буфер обмена</string> <string name="message_copied_to_clipboard">Сообщение скопировано в буфер обмена</string>
<string name="message">Сообщение</string> <string name="message">Сообщение</string>
<string name="private_messages_are_disabled">Личные сообщения выключены</string>
<string name="mtm_accept_cert">Принять Неизвестный Сертификат?</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_title">Редактировать статусное сообщение</string>
<string name="edit_status_message">Редактировать статусное сообщение</string> <string name="edit_status_message">Редактировать статусное сообщение</string>
<string name="disable_encryption">Отключить шифрование</string> <string name="disable_encryption">Отключить шифрование</string>
@ -626,16 +658,43 @@
<string name="disable_now">Отключить сейчас</string> <string name="disable_now">Отключить сейчас</string>
<string name="draft">Черновик:</string> <string name="draft">Черновик:</string>
<string name="pref_omemo_setting">OMEMO-шифрование</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">Размер шрифта</string>
<string name="pref_font_size_summary">Относительный размер шрифта используемый в приложении.</string>
<string name="default_on">Включено по умолчанию</string>
<string name="default_off">Выключено по умолчанию</string>
<string name="small">Маленький</string> <string name="small">Маленький</string>
<string name="medium">Средний</string> <string name="medium">Средний</string>
<string name="large">Большой</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="undo">отменить</string>
<string name="action_copy_location">Копировать местоположение</string> <string name="action_copy_location">Копировать местоположение</string>
<string name="action_share_location">Поделиться местоположением</string> <string name="action_share_location">Поделиться местоположением</string>
<string name="title_activity_share_location">Поделиться местоположением</string> <string name="title_activity_share_location">Поделиться местоположением</string>
<string name="title_activity_show_location">Показать местоположение</string> <string name="title_activity_show_location">Показать местоположение</string>
<string name="share">Поделиться</string> <string name="share">Поделиться</string>
<string name="unable_to_start_recording">Невозможно начать запись</string>
<string name="please_wait">Пожалуйста, подождите…</string> <string name="please_wait">Пожалуйста, подождите…</string>
<string name="no_microphone_permission">Conversations нужен доступ к микрофону</string>
<string name="search_messages">Поиск сообщений</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> </resources>

View File

@ -17,6 +17,8 @@
<string name="action_unblock_contact">Unblock contact</string> <string name="action_unblock_contact">Unblock contact</string>
<string name="action_block_domain">Block domain</string> <string name="action_block_domain">Block domain</string>
<string name="action_unblock_domain">Unblock 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_manage_accounts">Manage Accounts</string>
<string name="title_activity_settings">Settings</string> <string name="title_activity_settings">Settings</string>
<string name="title_activity_sharewith">Share with Conversation</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.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability; import com.google.android.gms.common.GoogleApiAvailability;
import com.google.firebase.iid.FirebaseInstanceId; import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.InstanceIdResult;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.utils.PhoneHelper; import eu.siacs.conversations.utils.PhoneHelper;
import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.XmppConnection;
import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.forms.Data;
import eu.siacs.conversations.xmpp.stanzas.IqPacket; import eu.siacs.conversations.xmpp.stanzas.IqPacket;
@ -25,18 +28,24 @@ public class PushManagementService {
this.mXmppConnectionService = service; this.mXmppConnectionService = service;
} }
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 Jid getAppServer() {
return Jid.of(mXmppConnectionService.getString(R.string.app_server));
}
void registerPushTokenOnServer(final Account account) { void registerPushTokenOnServer(final Account account) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": has push support"); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": has push support");
retrieveFcmInstanceToken(token -> { retrieveFcmInstanceToken(token -> {
final String androidId = PhoneHelper.getAndroidId(mXmppConnectionService); final String androidId = PhoneHelper.getAndroidId(mXmppConnectionService);
final Jid appServer = Jid.of(mXmppConnectionService.getString(R.string.app_server)); final IqPacket packet = mXmppConnectionService.getIqGenerator().pushTokenToAppServer(getAppServer(), token, androidId);
IqPacket packet = mXmppConnectionService.getIqGenerator().pushTokenToAppServer(appServer, token, androidId); mXmppConnectionService.sendIqPacket(account, packet, (a, response) -> {
mXmppConnectionService.sendIqPacket(account, packet, (a, p) -> { final Data data = findResponseData(response);
Element command = p.findChild("command", "http://jabber.org/protocol/commands"); if (response.getType() == IqPacket.TYPE.RESULT && data != null) {
if (p.getType() == IqPacket.TYPE.RESULT && command != null) {
Element x = command.findChild("x", Namespace.DATA);
if (x != null) {
Data data = Data.parse(x);
try { try {
String node = data.getValue("node"); String node = data.getValue("node");
String secret = data.getValue("secret"); String secret = data.getValue("secret");
@ -47,6 +56,45 @@ public class PushManagementService {
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
e.printStackTrace(); 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 { } else {
Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": invalid response from app server"); Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": invalid response from app server");
@ -55,8 +103,8 @@ public class PushManagementService {
}); });
} }
private void enablePushOnServer(final Account account, final Jid jid, final String node, final String secret) { private void enablePushOnServer(final Account account, final Jid appServer, final String node, final String secret) {
IqPacket enable = mXmppConnectionService.getIqGenerator().enablePush(jid, node, secret); final IqPacket enable = mXmppConnectionService.getIqGenerator().enablePush(appServer, node, secret);
mXmppConnectionService.sendIqPacket(account, enable, (a, p) -> { mXmppConnectionService.sendIqPacket(account, enable, (a, p) -> {
if (p.getType() == IqPacket.TYPE.RESULT) { if (p.getType() == IqPacket.TYPE.RESULT) {
Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": successfully enabled push on server"); Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": successfully enabled push on server");
@ -66,14 +114,48 @@ public class PushManagementService {
}); });
} }
private void retrieveFcmInstanceToken(final OnGcmInstanceTokenRetrieved instanceTokenRetrieved) { private void enablePushOnServer(final Conversation conversation, final Jid appServer, final String node, final String secret) {
new Thread(() -> { final Jid muc = conversation.getJid().asBareJid();
try { final IqPacket enable = mXmppConnectionService.getIqGenerator().enablePush(appServer, node, secret);
instanceTokenRetrieved.onGcmInstanceTokenRetrieved(FirebaseInstanceId.getInstance().getToken()); enable.setTo(muc);
} catch (Exception e) { mXmppConnectionService.sendIqPacket(conversation.getAccount(), enable, (a, p) -> {
Log.d(Config.LOGTAG, "unable to get push token",e); 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);
} }
}).start(); } 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());
}
});
} }

View File

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

View File

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

View File

@ -19,4 +19,5 @@
<string name="no_microphone_permission">Quicksy се нуждае от достъп до микрофона</string> <string name="no_microphone_permission">Quicksy се нуждае от достъп до микрофона</string>
<string name="foreground_service_channel_description">Тази категория известия се използва за показване на постоянно известие, което показва, че Quicksy работи.</string> <string name="foreground_service_channel_description">Тази категория известия се използва за показване на постоянно известие, което показва, че Quicksy работи.</string>
<string name="set_profile_picture">Профилна снимка за Quicksy</string> <string name="set_profile_picture">Профилна снимка за Quicksy</string>
<string name="not_available_in_your_country">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="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="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="set_profile_picture">Quicksy Profilbild</string>
<string name="not_available_in_your_country">Quicksy ist in deinem Land nicht verfügbar.</string>
</resources> </resources>

View File

@ -19,4 +19,5 @@
<string name="no_microphone_permission">Quicksy precisa acceder ao micrófono</string> <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="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="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> </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="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="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="set_profile_picture">Poză profil Quicksy</string>
<string name="not_available_in_your_country">Quicksy nu este disponibilă în țara dumneavoastră.</string>
</resources> </resources>

View File

@ -19,4 +19,5 @@
<string name="no_microphone_permission">Quicksy needs access to the microphone</string> <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="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="set_profile_picture">Quicksy profile picture</string>
<string name="not_available_in_your_country">Quicksy is not available in your country.</string>
</resources> </resources>