Merge tag '2.5.4' into develop
This commit is contained in:
commit
415a105b41
|
@ -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
|
||||||
|
|
14
build.gradle
14
build.gradle
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -71,7 +72,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
}
|
}
|
||||||
boolean both = contact.getOption(Contact.Options.TO) && contact.getOption(Contact.Options.FROM);
|
boolean both = contact.getOption(Contact.Options.TO) && contact.getOption(Contact.Options.FROM);
|
||||||
if ((both != bothPre) && both) {
|
if ((both != bothPre) && both) {
|
||||||
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": gained mutual presence subscription with "+contact.getJid());
|
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": gained mutual presence subscription with " + contact.getJid());
|
||||||
AxolotlService axolotlService = account.getAxolotlService();
|
AxolotlService axolotlService = account.getAxolotlService();
|
||||||
if (axolotlService != null) {
|
if (axolotlService != null) {
|
||||||
axolotlService.clearErrorsInFetchStatusMap(contact.getJid());
|
axolotlService.clearErrorsInFetchStatusMap(contact.getJid());
|
||||||
|
@ -124,7 +125,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
Integer id = Integer.valueOf(device.getAttribute("id"));
|
Integer id = Integer.valueOf(device.getAttribute("id"));
|
||||||
deviceIds.add(id);
|
deviceIds.add(id);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Encountered invalid <device> node in PEP ("+e.getMessage()+"):" + device.toString()+ ", skipping...");
|
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Encountered invalid <device> node in PEP (" + e.getMessage() + "):" + device.toString() + ", skipping...");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,7 +136,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
|
|
||||||
public Integer signedPreKeyId(final Element bundle) {
|
public Integer signedPreKeyId(final Element bundle) {
|
||||||
final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic");
|
final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic");
|
||||||
if(signedPreKeyPublic == null) {
|
if (signedPreKeyPublic == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -148,26 +149,26 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
public ECPublicKey signedPreKeyPublic(final Element bundle) {
|
public ECPublicKey signedPreKeyPublic(final Element bundle) {
|
||||||
ECPublicKey publicKey = null;
|
ECPublicKey publicKey = null;
|
||||||
final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic");
|
final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic");
|
||||||
if(signedPreKeyPublic == null) {
|
if (signedPreKeyPublic == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
publicKey = Curve.decodePoint(Base64.decode(signedPreKeyPublic.getContent(),Base64.DEFAULT), 0);
|
publicKey = Curve.decodePoint(Base64.decode(signedPreKeyPublic.getContent(), Base64.DEFAULT), 0);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Invalid signedPreKeyPublic in PEP: " + e.getMessage());
|
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Invalid signedPreKeyPublic in PEP: " + e.getMessage());
|
||||||
}
|
}
|
||||||
return publicKey;
|
return publicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] signedPreKeySignature(final Element bundle) {
|
public byte[] signedPreKeySignature(final Element bundle) {
|
||||||
final Element signedPreKeySignature = bundle.findChild("signedPreKeySignature");
|
final Element signedPreKeySignature = bundle.findChild("signedPreKeySignature");
|
||||||
if(signedPreKeySignature == null) {
|
if (signedPreKeySignature == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return Base64.decode(signedPreKeySignature.getContent(), Base64.DEFAULT);
|
return Base64.decode(signedPreKeySignature.getContent(), Base64.DEFAULT);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Log.e(Config.LOGTAG,AxolotlService.LOGPREFIX+" : Invalid base64 in signedPreKeySignature");
|
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : Invalid base64 in signedPreKeySignature");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,13 +176,13 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
public IdentityKey identityKey(final Element bundle) {
|
public IdentityKey identityKey(final Element bundle) {
|
||||||
IdentityKey identityKey = null;
|
IdentityKey identityKey = null;
|
||||||
final Element identityKeyElement = bundle.findChild("identityKey");
|
final Element identityKeyElement = bundle.findChild("identityKey");
|
||||||
if(identityKeyElement == null) {
|
if (identityKeyElement == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
identityKey = new IdentityKey(Base64.decode(identityKeyElement.getContent(), Base64.DEFAULT), 0);
|
identityKey = new IdentityKey(Base64.decode(identityKeyElement.getContent(), Base64.DEFAULT), 0);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Log.e(Config.LOGTAG,AxolotlService.LOGPREFIX+" : "+"Invalid identityKey in PEP: "+e.getMessage());
|
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Invalid identityKey in PEP: " + e.getMessage());
|
||||||
}
|
}
|
||||||
return identityKey;
|
return identityKey;
|
||||||
}
|
}
|
||||||
|
@ -190,21 +191,21 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
Map<Integer, ECPublicKey> preKeyRecords = new HashMap<>();
|
Map<Integer, ECPublicKey> preKeyRecords = new HashMap<>();
|
||||||
Element item = getItem(packet);
|
Element item = getItem(packet);
|
||||||
if (item == null) {
|
if (item == null) {
|
||||||
Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Couldn't find <item> in bundle IQ packet: " + packet);
|
Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Couldn't find <item> in bundle IQ packet: " + packet);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final Element bundleElement = item.findChild("bundle");
|
final Element bundleElement = item.findChild("bundle");
|
||||||
if(bundleElement == null) {
|
if (bundleElement == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final Element prekeysElement = bundleElement.findChild("prekeys");
|
final Element prekeysElement = bundleElement.findChild("prekeys");
|
||||||
if(prekeysElement == null) {
|
if (prekeysElement == null) {
|
||||||
Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Couldn't find <prekeys> in bundle IQ packet: " + packet);
|
Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Couldn't find <prekeys> in bundle IQ packet: " + packet);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
for(Element preKeyPublicElement : prekeysElement.getChildren()) {
|
for (Element preKeyPublicElement : prekeysElement.getChildren()) {
|
||||||
if(!preKeyPublicElement.getName().equals("preKeyPublic")){
|
if (!preKeyPublicElement.getName().equals("preKeyPublic")) {
|
||||||
Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Encountered unexpected tag in prekeys list: " + preKeyPublicElement);
|
Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Encountered unexpected tag in prekeys list: " + preKeyPublicElement);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Integer preKeyId = null;
|
Integer preKeyId = null;
|
||||||
|
@ -213,17 +214,17 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
final ECPublicKey preKeyPublic = Curve.decodePoint(Base64.decode(preKeyPublicElement.getContent(), Base64.DEFAULT), 0);
|
final ECPublicKey preKeyPublic = Curve.decodePoint(Base64.decode(preKeyPublicElement.getContent(), Base64.DEFAULT), 0);
|
||||||
preKeyRecords.put(preKeyId, preKeyPublic);
|
preKeyRecords.put(preKeyId, preKeyPublic);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"could not parse preKeyId from preKey "+preKeyPublicElement.toString());
|
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "could not parse preKeyId from preKey " + preKeyPublicElement.toString());
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Invalid preKeyPublic (ID="+preKeyId+") in PEP: "+ e.getMessage()+", skipping...");
|
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Invalid preKeyPublic (ID=" + preKeyId + ") in PEP: " + e.getMessage() + ", skipping...");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return preKeyRecords;
|
return preKeyRecords;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Pair<X509Certificate[],byte[]> verification(final IqPacket packet) {
|
public Pair<X509Certificate[], byte[]> verification(final IqPacket packet) {
|
||||||
Element item = getItem(packet);
|
Element item = getItem(packet);
|
||||||
Element verification = item != null ? item.findChild("verification",AxolotlService.PEP_PREFIX) : null;
|
Element verification = item != null ? item.findChild("verification", AxolotlService.PEP_PREFIX) : null;
|
||||||
Element chain = verification != null ? verification.findChild("chain") : null;
|
Element chain = verification != null ? verification.findChild("chain") : null;
|
||||||
Element signature = verification != null ? verification.findChild("signature") : null;
|
Element signature = verification != null ? verification.findChild("signature") : null;
|
||||||
if (chain != null && signature != null) {
|
if (chain != null && signature != null) {
|
||||||
|
@ -232,11 +233,11 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
try {
|
try {
|
||||||
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
|
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for(Element cert : certElements) {
|
for (Element cert : certElements) {
|
||||||
certificates[i] = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(Base64.decode(cert.getContent(),Base64.DEFAULT)));
|
certificates[i] = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(Base64.decode(cert.getContent(), Base64.DEFAULT)));
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
return new Pair<>(certificates,Base64.decode(signature.getContent(),Base64.DEFAULT));
|
return new Pair<>(certificates, Base64.decode(signature.getContent(), Base64.DEFAULT));
|
||||||
} catch (CertificateException e) {
|
} catch (CertificateException e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -247,18 +248,18 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
|
|
||||||
public PreKeyBundle bundle(final IqPacket bundle) {
|
public PreKeyBundle bundle(final IqPacket bundle) {
|
||||||
Element bundleItem = getItem(bundle);
|
Element bundleItem = getItem(bundle);
|
||||||
if(bundleItem == null) {
|
if (bundleItem == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final Element bundleElement = bundleItem.findChild("bundle");
|
final Element bundleElement = bundleItem.findChild("bundle");
|
||||||
if(bundleElement == null) {
|
if (bundleElement == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
ECPublicKey signedPreKeyPublic = signedPreKeyPublic(bundleElement);
|
ECPublicKey signedPreKeyPublic = signedPreKeyPublic(bundleElement);
|
||||||
Integer signedPreKeyId = signedPreKeyId(bundleElement);
|
Integer signedPreKeyId = signedPreKeyId(bundleElement);
|
||||||
byte[] signedPreKeySignature = signedPreKeySignature(bundleElement);
|
byte[] signedPreKeySignature = signedPreKeySignature(bundleElement);
|
||||||
IdentityKey identityKey = identityKey(bundleElement);
|
IdentityKey identityKey = identityKey(bundleElement);
|
||||||
if(signedPreKeyId == null || signedPreKeyPublic == null || identityKey == null) {
|
if (signedPreKeyId == null || signedPreKeyPublic == null || identityKey == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,7 +270,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
public List<PreKeyBundle> preKeys(final IqPacket preKeys) {
|
public List<PreKeyBundle> preKeys(final IqPacket preKeys) {
|
||||||
List<PreKeyBundle> bundles = new ArrayList<>();
|
List<PreKeyBundle> bundles = new ArrayList<>();
|
||||||
Map<Integer, ECPublicKey> preKeyPublics = preKeyPublics(preKeys);
|
Map<Integer, ECPublicKey> preKeyPublics = preKeyPublics(preKeys);
|
||||||
if ( preKeyPublics != null) {
|
if (preKeyPublics != null) {
|
||||||
for (Integer preKeyId : preKeyPublics.keySet()) {
|
for (Integer preKeyId : preKeyPublics.keySet()) {
|
||||||
ECPublicKey preKeyPublic = preKeyPublics.get(preKeyId);
|
ECPublicKey preKeyPublic = preKeyPublics.get(preKeyId);
|
||||||
bundles.add(new PreKeyBundle(0, 0, preKeyId, preKeyPublic,
|
bundles.add(new PreKeyBundle(0, 0, preKeyId, preKeyPublic,
|
||||||
|
@ -321,8 +322,8 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
account.getBlocklist().addAll(jids);
|
account.getBlocklist().addAll(jids);
|
||||||
if (packet.getType() == IqPacket.TYPE.SET) {
|
if (packet.getType() == IqPacket.TYPE.SET) {
|
||||||
boolean removed = false;
|
boolean removed = false;
|
||||||
for(Jid jid : jids) {
|
for (Jid jid : jids) {
|
||||||
removed |= mXmppConnectionService.removeBlockedConversations(account,jid);
|
removed |= mXmppConnectionService.removeBlockedConversations(account, jid);
|
||||||
}
|
}
|
||||||
if (removed) {
|
if (removed) {
|
||||||
mXmppConnectionService.updateConversationUi();
|
mXmppConnectionService.updateConversationUi();
|
||||||
|
@ -359,35 +360,58 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
mXmppConnectionService.sendIqPacket(account, response, null);
|
mXmppConnectionService.sendIqPacket(account, response, null);
|
||||||
} else if (packet.hasChild("open", "http://jabber.org/protocol/ibb")
|
} else if (packet.hasChild("open", "http://jabber.org/protocol/ibb")
|
||||||
|| packet.hasChild("data", "http://jabber.org/protocol/ibb")
|
|| packet.hasChild("data", "http://jabber.org/protocol/ibb")
|
||||||
|| packet.hasChild("close","http://jabber.org/protocol/ibb")) {
|
|| packet.hasChild("close", "http://jabber.org/protocol/ibb")) {
|
||||||
mXmppConnectionService.getJingleConnectionManager()
|
mXmppConnectionService.getJingleConnectionManager()
|
||||||
.deliverIbbPacket(account, packet);
|
.deliverIbbPacket(account, packet);
|
||||||
} else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) {
|
} else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) {
|
||||||
final IqPacket response = mXmppConnectionService.getIqGenerator().discoResponse(account, packet);
|
final IqPacket response = mXmppConnectionService.getIqGenerator().discoResponse(account, packet);
|
||||||
mXmppConnectionService.sendIqPacket(account, response, null);
|
mXmppConnectionService.sendIqPacket(account, response, null);
|
||||||
} else if (packet.hasChild("query","jabber:iq:version") && isGet) {
|
} else if (packet.hasChild("query", "jabber:iq:version") && isGet) {
|
||||||
final IqPacket response = mXmppConnectionService.getIqGenerator().versionResponse(packet);
|
final IqPacket response = mXmppConnectionService.getIqGenerator().versionResponse(packet);
|
||||||
mXmppConnectionService.sendIqPacket(account,response,null);
|
mXmppConnectionService.sendIqPacket(account, response, null);
|
||||||
} else if (packet.hasChild("ping", "urn:xmpp:ping") && isGet) {
|
} else if (packet.hasChild("ping", "urn:xmpp:ping") && isGet) {
|
||||||
final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT);
|
final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT);
|
||||||
mXmppConnectionService.sendIqPacket(account, response, null);
|
mXmppConnectionService.sendIqPacket(account, response, null);
|
||||||
} else if (packet.hasChild("time","urn:xmpp:time") && isGet) {
|
} else if (packet.hasChild("time", "urn:xmpp:time") && isGet) {
|
||||||
final IqPacket response;
|
final IqPacket response;
|
||||||
if (mXmppConnectionService.useTorToConnect() || account.isOnion()) {
|
if (mXmppConnectionService.useTorToConnect() || account.isOnion()) {
|
||||||
response = packet.generateResponse(IqPacket.TYPE.ERROR);
|
response = packet.generateResponse(IqPacket.TYPE.ERROR);
|
||||||
final Element error = response.addChild("error");
|
final Element error = response.addChild("error");
|
||||||
error.setAttribute("type","cancel");
|
error.setAttribute("type", "cancel");
|
||||||
error.addChild("not-allowed","urn:ietf:params:xml:ns:xmpp-stanzas");
|
error.addChild("not-allowed", "urn:ietf:params:xml:ns:xmpp-stanzas");
|
||||||
} else {
|
} else {
|
||||||
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);
|
||||||
final Element error = response.addChild("error");
|
final Element error = response.addChild("error");
|
||||||
error.setAttribute("type", "cancel");
|
error.setAttribute("type", "cancel");
|
||||||
error.addChild("feature-not-implemented","urn:ietf:params:xml:ns:xmpp-stanzas");
|
error.addChild("feature-not-implemented", "urn:ietf:params:xml:ns:xmpp-stanzas");
|
||||||
account.getXmppConnection().sendIqPacket(response, null);
|
account.getXmppConnection().sendIqPacket(response, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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";
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -6,4 +6,4 @@
|
||||||
<string name="pref_broadcast_last_activity_summary">إجعل كلّ جهات إتصالك تعلم أنك تستعمل كويكسي</string>
|
<string name="pref_broadcast_last_activity_summary">إجعل كلّ جهات إتصالك تعلم أنك تستعمل كويكسي</string>
|
||||||
<string name="no_microphone_permission">كويكسي يحتاج الإتصال بالمايكروفون</string>
|
<string name="no_microphone_permission">كويكسي يحتاج الإتصال بالمايكروفون</string>
|
||||||
<string name="set_profile_picture">صورة حساب كويكسي</string>
|
<string name="set_profile_picture">صورة حساب كويكسي</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -19,4 +19,4 @@
|
||||||
<string name="no_microphone_permission">Quicksy necessita accés al micròfon</string>
|
<string name="no_microphone_permission">Quicksy necessita accés al micròfon</string>
|
||||||
<string name="foreground_service_channel_description">Aquest tipus de notificació s\'utilitza per mostrar una notificació permanent que indica que Quicksy s\'està executant.</string>
|
<string name="foreground_service_channel_description">Aquest tipus de notificació s\'utilitza per mostrar una notificació permanent que indica que Quicksy s\'està executant.</string>
|
||||||
<string name="set_profile_picture">Imatge de perfil en Quicksy</string>
|
<string name="set_profile_picture">Imatge de perfil en Quicksy</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -19,4 +19,4 @@
|
||||||
<string name="no_microphone_permission">Quicksy necesita acceder al micrófono</string>
|
<string name="no_microphone_permission">Quicksy necesita acceder al micrófono</string>
|
||||||
<string name="foreground_service_channel_description">Esta categoría de notificación se usa para mostrar una notificación permantente indicando que Quicksy está ejecutándose.</string>
|
<string name="foreground_service_channel_description">Esta categoría de notificación se usa para mostrar una notificación permantente indicando que Quicksy está ejecutándose.</string>
|
||||||
<string name="set_profile_picture">Foto de perfil en Quicksy</string>
|
<string name="set_profile_picture">Foto de perfil en Quicksy</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -19,4 +19,4 @@
|
||||||
<string name="no_microphone_permission">Quicksy doit avoir accès au microphone</string>
|
<string name="no_microphone_permission">Quicksy doit avoir accès au microphone</string>
|
||||||
<string name="foreground_service_channel_description">Cette catégorie de notification est utilisée pour afficher une notification permanente indiquant que Quicksy est en cours d\'exécution.</string>
|
<string name="foreground_service_channel_description">Cette catégorie de notification est utilisée pour afficher une notification permanente indiquant que Quicksy est en cours d\'exécution.</string>
|
||||||
<string name="set_profile_picture">Photo de profil Quicksy</string>
|
<string name="set_profile_picture">Photo de profil Quicksy</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -19,4 +19,4 @@
|
||||||
<string name="no_microphone_permission">A Quicksy-nek hozzáférésre lenne szüksége a mikrofonhoz</string>
|
<string name="no_microphone_permission">A Quicksy-nek hozzáférésre lenne szüksége a mikrofonhoz</string>
|
||||||
<string name="foreground_service_channel_description">Ez az értesítési kategória állandó értesítést jelenít meg arról, hogy a Quicksy fut.</string>
|
<string name="foreground_service_channel_description">Ez az értesítési kategória állandó értesítést jelenít meg arról, hogy a Quicksy fut.</string>
|
||||||
<string name="set_profile_picture">Quicksy profilkép</string>
|
<string name="set_profile_picture">Quicksy profilkép</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -19,4 +19,4 @@
|
||||||
<string name="no_microphone_permission">Quicksy ha bisogno di accedere al microfono</string>
|
<string name="no_microphone_permission">Quicksy ha bisogno di accedere al microfono</string>
|
||||||
<string name="foreground_service_channel_description">Questa categoria di notifiche è usata per mostrare una notifica permanente per indicare che Quicksy è in esecuzione.</string>
|
<string name="foreground_service_channel_description">Questa categoria di notifiche è usata per mostrare una notifica permanente per indicare che Quicksy è in esecuzione.</string>
|
||||||
<string name="set_profile_picture">Immagine profilo di Quicksy</string>
|
<string name="set_profile_picture">Immagine profilo di Quicksy</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -19,4 +19,4 @@
|
||||||
<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>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -19,4 +19,4 @@
|
||||||
<string name="no_microphone_permission">Quicksy heeft toegang nodig tot de microfoon</string>
|
<string name="no_microphone_permission">Quicksy heeft toegang nodig tot de microfoon</string>
|
||||||
<string name="foreground_service_channel_description">Deze meldingscategorie wordt gebruikt om een permanente melding weer te geven dat Quicksy wordt uitgevoerd.</string>
|
<string name="foreground_service_channel_description">Deze meldingscategorie wordt gebruikt om een permanente melding weer te geven dat Quicksy wordt uitgevoerd.</string>
|
||||||
<string name="set_profile_picture">Quicksy-profielafbeelding</string>
|
<string name="set_profile_picture">Quicksy-profielafbeelding</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -19,4 +19,4 @@
|
||||||
<string name="no_microphone_permission">Quicksy potrzebuje dostępu do mikrofonu.</string>
|
<string name="no_microphone_permission">Quicksy potrzebuje dostępu do mikrofonu.</string>
|
||||||
<string name="foreground_service_channel_description">Ta kategoria powiadomień jest używana do wyświetlania ciągłego powiadomienia o tym, że Quicksy działa.</string>
|
<string name="foreground_service_channel_description">Ta kategoria powiadomień jest używana do wyświetlania ciągłego powiadomienia o tym, że Quicksy działa.</string>
|
||||||
<string name="set_profile_picture">Obrazek profilowy Quicksy</string>
|
<string name="set_profile_picture">Obrazek profilowy Quicksy</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -19,4 +19,4 @@
|
||||||
<string name="no_microphone_permission">O Quicksy necessita de acesso ao microfone</string>
|
<string name="no_microphone_permission">O Quicksy necessita de acesso ao microfone</string>
|
||||||
<string name="foreground_service_channel_description">Essa categoria de notificação é utilizada para exibir uma notificação permanente indicando que o Quicksy está em execução.</string>
|
<string name="foreground_service_channel_description">Essa categoria de notificação é utilizada para exibir uma notificação permanente indicando que o Quicksy está em execução.</string>
|
||||||
<string name="set_profile_picture">Imagem de perfil do Quicksy</string>
|
<string name="set_profile_picture">Imagem de perfil do Quicksy</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -19,4 +19,4 @@
|
||||||
<string name="no_microphone_permission">Програма потребує доступу до мікрофона</string>
|
<string name="no_microphone_permission">Програма потребує доступу до мікрофона</string>
|
||||||
<string name="foreground_service_channel_description">Цей вид сповіщень показує постійне сповіщення про те, що ця програма працює.</string>
|
<string name="foreground_service_channel_description">Цей вид сповіщень показує постійне сповіщення про те, що ця програма працює.</string>
|
||||||
<string name="set_profile_picture">Зображення профілю для Quicksy</string>
|
<string name="set_profile_picture">Зображення профілю для Quicksy</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -19,4 +19,4 @@
|
||||||
<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>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue