Merge tag '2.6.1' into develop
This commit is contained in:
commit
d4f7c2b34a
|
@ -1,5 +1,10 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
### Version 2.6.1
|
||||||
|
* fixes for Jingle IBB file transfer
|
||||||
|
* fixes for repeated corrections filling up the database
|
||||||
|
* switched to Last Message Correction v1.1
|
||||||
|
|
||||||
### Version 2.6.0
|
### Version 2.6.0
|
||||||
* Introduce expert setting to perform channel discovery on local server instead of [search.jabber.network](https://search.jabber.network)
|
* Introduce expert setting to perform channel discovery on local server instead of [search.jabber.network](https://search.jabber.network)
|
||||||
* Enable delivery check marks by default and remove setting
|
* Enable delivery check marks by default and remove setting
|
||||||
|
|
|
@ -354,8 +354,6 @@ Read more about the concept on https://gultsch.de/trust.html
|
||||||
#### What happened to OTR support?
|
#### What happened to OTR support?
|
||||||
OTR was removed because it was highly unreliable. It didn’t work with multiple devices and was never really specified to work with XMPP. The codebase was a mess (There was an HTML parser in there for crying out loud to deal with the garbage some OTR clients would send.) Verification was implemented in a non-blocking way. It would tell you if the current session was using an unknown fingerprint but it didn’t actively stopped you from sending messages until you have confirmed the new fingerprint. (Like Conversations would do now with BTBV after verification or when BTBV is turned off.) Considering the previous points there was little to no desire from my point to fix this potential security issue or clean up the code base. Another reason for the removal was that people would use it *accidentally* even to communicate between two Conversations clients because they read somewhere that OTR is good.
|
OTR was removed because it was highly unreliable. It didn’t work with multiple devices and was never really specified to work with XMPP. The codebase was a mess (There was an HTML parser in there for crying out loud to deal with the garbage some OTR clients would send.) Verification was implemented in a non-blocking way. It would tell you if the current session was using an unknown fingerprint but it didn’t actively stopped you from sending messages until you have confirmed the new fingerprint. (Like Conversations would do now with BTBV after verification or when BTBV is turned off.) Considering the previous points there was little to no desire from my point to fix this potential security issue or clean up the code base. Another reason for the removal was that people would use it *accidentally* even to communicate between two Conversations clients because they read somewhere that OTR is good.
|
||||||
|
|
||||||
OTR is still available in [Conversations Legacy](https://github.com/siacs/Conversations/tree/legacy).
|
|
||||||
|
|
||||||
### What clients do I use on other platforms
|
### What clients do I use on other platforms
|
||||||
There are XMPP Clients available for all major platforms.
|
There are XMPP Clients available for all major platforms.
|
||||||
#### Windows / Linux
|
#### Windows / Linux
|
||||||
|
|
|
@ -84,8 +84,8 @@ android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 25
|
targetSdkVersion 25
|
||||||
versionCode 350
|
versionCode 351
|
||||||
versionName "2.6.0.1"
|
versionName "2.6.1"
|
||||||
archivesBaseName += "-$versionName"
|
archivesBaseName += "-$versionName"
|
||||||
applicationId "eu.sum7.conversations"
|
applicationId "eu.sum7.conversations"
|
||||||
resValue "string", "applicationId", applicationId
|
resValue "string", "applicationId", applicationId
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<string name="pick_a_server">Wähle deinen XMPP-Provider</string>
|
<string name="pick_a_server">Wähle deinen XMPP-Provider</string>
|
||||||
<string name="use_chat.sum7.eu">Benutze chat.sum7.eu</string>
|
<string name="use_chat.sum7.eu">Benutze chat.sum7.eu</string>
|
||||||
<string name="create_new_account">Neues Konto erstellen</string>
|
<string name="create_new_account">Neues Konto erstellen</string>
|
||||||
<string name="do_you_have_an_account">Hast du bereits ein XMPP-Konto? Dies kann der Fall sein, wenn du bereits einen anderen XMPP-Client verwendest oder bereits Conversations verwendet hast. Wenn nicht, kannst du jetzt ein neues XMPP-Konto erstellen.\nHinweis: Einige E-Mail-Anbieter bieten auch XMPP-Konten an.</string>
|
<string name="do_you_have_an_account">Hast du bereits ein XMPP-Konto? Dies kann der Fall sein, wenn du bereits einen anderen XMPP-Client verwendest oder bereits Conv6sations verwendet hast. Wenn nicht, kannst du jetzt ein neues XMPP-Konto erstellen.\nTipp: Einige E-Mail-Anbieter bieten auch XMPP-Konten an.</string>
|
||||||
<string name="server_select_text">XMPP ist ein anbieterunabhängiges Instant Messaging Netzwerk. Du kannst diesen Client mit jedem beliebigen XMPP-Server nutzen.\nUm es dir leicht zu machen, haben wir die Möglichkeit geschaffen, ein Konto auf chat.sum7.eu anzulegen; ein Anbieter, der speziell für die Verwendung mit Conversations geeignet ist.</string>
|
<string name="server_select_text">XMPP ist ein anbieterunabhängiges Instant Messaging Netzwerk. Du kannst diesen Client mit jedem beliebigen XMPP-Server nutzen.\nUm es dir leicht zu machen, haben wir die Möglichkeit geschaffen, ein Konto auf chat.sum7.eu anzulegen; ein Anbieter, der speziell für die Verwendung mit Conv6sations geeignet ist.</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="pick_a_server">Válassza ki az XMPP szolgáltatóját</string>
|
<string name="pick_a_server">Válassza ki az XMPP szolgáltatóját</string>
|
||||||
<string name="use_chat.sum7.eu">chat.sum7.eu használata</string>
|
<string name="use_chat.sum7.eu">A chat.sum7.eu használata</string>
|
||||||
<string name="create_new_account">Új fiók létrehozása</string>
|
<string name="create_new_account">Új fiók létrehozása</string>
|
||||||
<string name="do_you_have_an_account">Már rendelkezik XMPP fiókkal? Ez az eset állhat fenn, ha már egy másik XMPP klienst használ vagy ha már korábban használta a Conversations-t. Ha nem, akkor most létrehozhat egy új XMPP fiókot.\nTipp: Egyes e-mail szolgáltatók is biztosítanak XMPP fiókokat is.</string>
|
<string name="do_you_have_an_account">Már rendelkezik XMPP-fiókkal? Ez az eset állhat fenn, ha már egy másik XMPP-klienst használ, vagy ha már korábban használta a Conv6sations alkalmazást. Ha nem, akkor most létrehozhat egy új XMPP-fiókot.\nTipp: egyes e-mail szolgáltatók is biztosítanak XMPP-fiókokat.</string>
|
||||||
<string name="server_select_text">Az XMPP egy szolgáltató független, azonnali üzenetküldő hálózat. Ezt az ügyfél programot bármelyik XMPP szerverhez használhatja.\nDe a könnyebbség kedvéért létrehozhat egy fiókot a chat.sum7.eu szolgáltatón is; ami kifejezetten a Conversations programmal való használatra lett tervezve.</string>
|
<string name="server_select_text">Az XMPP egy szolgáltatófüggetlen, azonnali üzenetküldő hálózat. Ezt a kliensprogramot bármely XMPP-kiszolgálóhoz használhatja.\nAzonban a kényelem érdekében megkönnyítettük a chat.sum7.eu szolgáltatón való fióklétrehozást, ami kifejezetten a Conv6sations alkalmazással történő használatra lett tervezve.</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -90,7 +90,7 @@ public final class Config {
|
||||||
public static final int REFRESH_UI_INTERVAL = 500;
|
public static final int REFRESH_UI_INTERVAL = 500;
|
||||||
|
|
||||||
public static final int MAX_DISPLAY_MESSAGE_CHARS = 4096;
|
public static final int MAX_DISPLAY_MESSAGE_CHARS = 4096;
|
||||||
public static final int MAX_STORAGE_MESSAGE_CHARS = 1024 * 1024; //1MB
|
public static final int MAX_STORAGE_MESSAGE_CHARS = 2 * 1024 * 1024; //2MB
|
||||||
|
|
||||||
public static final long MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
|
public static final long MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ public final class Config {
|
||||||
public static final boolean IGNORE_ID_REWRITE_IN_MUC = true;
|
public static final boolean IGNORE_ID_REWRITE_IN_MUC = true;
|
||||||
public static final boolean MUC_LEAVE_BEFORE_JOIN = true;
|
public static final boolean MUC_LEAVE_BEFORE_JOIN = true;
|
||||||
|
|
||||||
public static final boolean USE_LMC_VERSION_1_1 = false;
|
public static final boolean USE_LMC_VERSION_1_1 = true;
|
||||||
|
|
||||||
public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY * 5;
|
public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY * 5;
|
||||||
public static final int MAM_MAX_MESSAGES = 750;
|
public static final int MAM_MAX_MESSAGES = 750;
|
||||||
|
|
|
@ -843,7 +843,8 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
final String node = AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId();
|
final String node = AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId();
|
||||||
final IqPacket deleteBundleNode = mXmppConnectionService.getIqGenerator().deleteNode(node);
|
final IqPacket deleteBundleNode = mXmppConnectionService.getIqGenerator().deleteNode(node);
|
||||||
mXmppConnectionService.sendIqPacket(account, deleteBundleNode, null);
|
mXmppConnectionService.sendIqPacket(account, deleteBundleNode, null);
|
||||||
publishDeviceIdsAndRefineAccessModel(getOwnDeviceIds());
|
final Set<Integer> ownDeviceIds = getOwnDeviceIds();
|
||||||
|
publishDeviceIdsAndRefineAccessModel(ownDeviceIds == null ? Collections.emptySet() : ownDeviceIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Jid> getCryptoTargets(Conversation conversation) {
|
public List<Jid> getCryptoTargets(Conversation conversation) {
|
||||||
|
|
|
@ -338,7 +338,9 @@ public class Contact implements ListItem, Blockable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean showInContactList() {
|
public boolean showInContactList() {
|
||||||
return showInRoster() || getOption(Options.SYNCED_VIA_OTHER);
|
return showInRoster()
|
||||||
|
|| getOption(Options.SYNCED_VIA_OTHER)
|
||||||
|
|| (QuickConversationsService.isQuicksy() && systemAccount != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void parseSubscriptionFromElement(Element item) {
|
public void parseSubscriptionFromElement(Element item) {
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
package eu.siacs.conversations.entities;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Edit {
|
||||||
|
|
||||||
|
private final String editedId;
|
||||||
|
private final String serverMsgId;
|
||||||
|
|
||||||
|
Edit(String editedId, String serverMsgId) {
|
||||||
|
this.editedId = editedId;
|
||||||
|
this.serverMsgId = serverMsgId;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String toJson(List<Edit> edits) throws JSONException {
|
||||||
|
JSONArray jsonArray = new JSONArray();
|
||||||
|
for (Edit edit : edits) {
|
||||||
|
jsonArray.put(edit.toJson());
|
||||||
|
}
|
||||||
|
return jsonArray.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean wasPreviouslyEditedRemoteMsgId(List<Edit> edits, String remoteMsgId) {
|
||||||
|
for (Edit edit : edits) {
|
||||||
|
if (edit.editedId != null && edit.editedId.equals(remoteMsgId)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean wasPreviouslyEditedServerMsgId(List<Edit> edits, String serverMsgId) {
|
||||||
|
for (Edit edit : edits) {
|
||||||
|
if (edit.serverMsgId != null && edit.serverMsgId.equals(serverMsgId)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Edit fromJson(JSONObject jsonObject) throws JSONException {
|
||||||
|
String edited = jsonObject.has("edited_id") ? jsonObject.getString("edited_id") : null;
|
||||||
|
String serverMsgId = jsonObject.has("server_msg_id") ? jsonObject.getString("server_msg_id") : null;
|
||||||
|
return new Edit(edited, serverMsgId);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Edit> fromJson(String input) {
|
||||||
|
final ArrayList<Edit> list = new ArrayList<>();
|
||||||
|
if (input == null) {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final JSONArray jsonArray = new JSONArray(input);
|
||||||
|
for (int i = 0; i < jsonArray.length(); ++i) {
|
||||||
|
list.add(fromJson(jsonArray.getJSONObject(i)));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
} catch (JSONException e) {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private JSONObject toJson() throws JSONException {
|
||||||
|
JSONObject jsonObject = new JSONObject();
|
||||||
|
jsonObject.put("edited_id", editedId);
|
||||||
|
jsonObject.put("server_msg_id", serverMsgId);
|
||||||
|
return jsonObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getEditedId() {
|
||||||
|
return editedId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
Edit edit = (Edit) o;
|
||||||
|
|
||||||
|
if (editedId != null ? !editedId.equals(edit.editedId) : edit.editedId != null)
|
||||||
|
return false;
|
||||||
|
return serverMsgId != null ? serverMsgId.equals(edit.serverMsgId) : edit.serverMsgId == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = editedId != null ? editedId.hashCode() : 0;
|
||||||
|
result = 31 * result + (serverMsgId != null ? serverMsgId.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,80 +0,0 @@
|
||||||
package eu.siacs.conversations.entities;
|
|
||||||
|
|
||||||
import org.json.JSONArray;
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class Edited {
|
|
||||||
|
|
||||||
private final String editedId;
|
|
||||||
private final String serverMsgId;
|
|
||||||
|
|
||||||
public Edited(String editedId, String serverMsgId) {
|
|
||||||
this.editedId = editedId;
|
|
||||||
this.serverMsgId = serverMsgId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String toJson(List<Edited> edits) throws JSONException {
|
|
||||||
JSONArray jsonArray = new JSONArray();
|
|
||||||
for (Edited edited : edits) {
|
|
||||||
jsonArray.put(edited.toJson());
|
|
||||||
}
|
|
||||||
return jsonArray.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean wasPreviouslyEditedRemoteMsgId(List<Edited> editeds, String remoteMsgId) {
|
|
||||||
for (Edited edited : editeds) {
|
|
||||||
if (edited.editedId != null && edited.editedId.equals(remoteMsgId)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean wasPreviouslyEditedServerMsgId(List<Edited> editeds, String serverMsgId) {
|
|
||||||
for (Edited edited : editeds) {
|
|
||||||
if (edited.serverMsgId != null && edited.serverMsgId.equals(serverMsgId)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Edited fromJson(JSONObject jsonObject) throws JSONException {
|
|
||||||
String edited = jsonObject.getString("edited_id");
|
|
||||||
String serverMsgId = jsonObject.getString("server_msg_id");
|
|
||||||
return new Edited(edited, serverMsgId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<Edited> fromJson(String input) {
|
|
||||||
ArrayList<Edited> list = new ArrayList<>();
|
|
||||||
if (input == null) {
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
JSONArray jsonArray = new JSONArray(input);
|
|
||||||
for (int i = 0; i < jsonArray.length(); ++i) {
|
|
||||||
list.add(fromJson(jsonArray.getJSONObject(i)));
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (JSONException e) {
|
|
||||||
list = new ArrayList<>();
|
|
||||||
list.add(new Edited(input, null));
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JSONObject toJson() throws JSONException {
|
|
||||||
JSONObject jsonObject = new JSONObject();
|
|
||||||
jsonObject.put("edited_id", editedId);
|
|
||||||
jsonObject.put("server_msg_id", serverMsgId);
|
|
||||||
return jsonObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getEditedId() {
|
|
||||||
return editedId;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -97,7 +97,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
protected boolean deleted = false;
|
protected boolean deleted = false;
|
||||||
protected boolean carbon = false;
|
protected boolean carbon = false;
|
||||||
protected boolean oob = false;
|
protected boolean oob = false;
|
||||||
protected List<Edited> edits = new ArrayList<>();
|
protected List<Edit> edits = new ArrayList<>();
|
||||||
protected String relativeFilePath;
|
protected String relativeFilePath;
|
||||||
protected boolean read = true;
|
protected boolean read = true;
|
||||||
protected String remoteMsgId = null;
|
protected String remoteMsgId = null;
|
||||||
|
@ -174,7 +174,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
this.serverMsgId = serverMsgId;
|
this.serverMsgId = serverMsgId;
|
||||||
this.axolotlFingerprint = fingerprint;
|
this.axolotlFingerprint = fingerprint;
|
||||||
this.read = read;
|
this.read = read;
|
||||||
this.edits = Edited.fromJson(edited);
|
this.edits = Edit.fromJson(edited);
|
||||||
this.oob = oob;
|
this.oob = oob;
|
||||||
this.errorMessage = errorMessage;
|
this.errorMessage = errorMessage;
|
||||||
this.readByMarkers = readByMarkers == null ? new HashSet<>() : readByMarkers;
|
this.readByMarkers = readByMarkers == null ? new HashSet<>() : readByMarkers;
|
||||||
|
@ -263,7 +263,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
values.put(FINGERPRINT, axolotlFingerprint);
|
values.put(FINGERPRINT, axolotlFingerprint);
|
||||||
values.put(READ, read ? 1 : 0);
|
values.put(READ, read ? 1 : 0);
|
||||||
try {
|
try {
|
||||||
values.put(EDITED, Edited.toJson(edits));
|
values.put(EDITED, Edit.toJson(edits));
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
Log.e(Config.LOGTAG,"error persisting json for edits",e);
|
Log.e(Config.LOGTAG,"error persisting json for edits",e);
|
||||||
}
|
}
|
||||||
|
@ -434,11 +434,14 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
}
|
}
|
||||||
|
|
||||||
public void putEdited(String edited, String serverMsgId) {
|
public void putEdited(String edited, String serverMsgId) {
|
||||||
this.edits.add(new Edited(edited, serverMsgId));
|
final Edit edit = new Edit(edited, serverMsgId);
|
||||||
|
if (this.edits.size() < 128 && !this.edits.contains(edit)) {
|
||||||
|
this.edits.add(edit);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean remoteMsgIdMatchInEdit(String id) {
|
boolean remoteMsgIdMatchInEdit(String id) {
|
||||||
for(Edited edit : this.edits) {
|
for(Edit edit : this.edits) {
|
||||||
if (id.equals(edit.getEditedId())) {
|
if (id.equals(edit.getEditedId())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -507,8 +510,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
|
|
||||||
boolean similar(Message message) {
|
boolean similar(Message message) {
|
||||||
if (!isPrivateMessage() && this.serverMsgId != null && message.getServerMsgId() != null) {
|
if (!isPrivateMessage() && this.serverMsgId != null && message.getServerMsgId() != null) {
|
||||||
return this.serverMsgId.equals(message.getServerMsgId()) || Edited.wasPreviouslyEditedServerMsgId(edits, message.getServerMsgId());
|
return this.serverMsgId.equals(message.getServerMsgId()) || Edit.wasPreviouslyEditedServerMsgId(edits, message.getServerMsgId());
|
||||||
} else if (Edited.wasPreviouslyEditedServerMsgId(edits, message.getServerMsgId())) {
|
} else if (Edit.wasPreviouslyEditedServerMsgId(edits, message.getServerMsgId())) {
|
||||||
return true;
|
return true;
|
||||||
} else if (this.body == null || this.counterpart == null) {
|
} else if (this.body == null || this.counterpart == null) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -524,7 +527,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
final boolean matchingCounterpart = this.counterpart.equals(message.getCounterpart());
|
final boolean matchingCounterpart = this.counterpart.equals(message.getCounterpart());
|
||||||
if (message.getRemoteMsgId() != null) {
|
if (message.getRemoteMsgId() != null) {
|
||||||
final boolean hasUuid = CryptoHelper.UUID_PATTERN.matcher(message.getRemoteMsgId()).matches();
|
final boolean hasUuid = CryptoHelper.UUID_PATTERN.matcher(message.getRemoteMsgId()).matches();
|
||||||
if (hasUuid && matchingCounterpart && Edited.wasPreviouslyEditedRemoteMsgId(edits, message.getRemoteMsgId())) {
|
if (hasUuid && matchingCounterpart && Edit.wasPreviouslyEditedRemoteMsgId(edits, message.getRemoteMsgId())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return (message.getRemoteMsgId().equals(this.remoteMsgId) || message.getRemoteMsgId().equals(this.uuid))
|
return (message.getRemoteMsgId().equals(this.remoteMsgId) || message.getRemoteMsgId().equals(this.uuid))
|
||||||
|
|
|
@ -158,7 +158,7 @@ public class ServiceDiscoveryResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getVer() {
|
public String getVer() {
|
||||||
return new String(Base64.encode(this.ver, Base64.DEFAULT)).trim();
|
return Base64.encodeToString(this.ver, Base64.NO_WRAP);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Identity> getIdentities() {
|
public List<Identity> getIdentities() {
|
||||||
|
|
|
@ -99,8 +99,8 @@ public abstract class AbstractGenerator {
|
||||||
for (String feature : getFeatures(account)) {
|
for (String feature : getFeatures(account)) {
|
||||||
s.append(feature).append('<');
|
s.append(feature).append('<');
|
||||||
}
|
}
|
||||||
byte[] sha1 = md.digest(s.toString().getBytes());
|
final byte[] sha1 = md.digest(s.toString().getBytes());
|
||||||
return new String(Base64.encode(sha1, Base64.DEFAULT)).trim();
|
return Base64.encodeToString(sha1, Base64.NO_WRAP);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getTimestamp(long time) {
|
public static String getTimestamp(long time) {
|
||||||
|
|
|
@ -160,10 +160,6 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return publish("urn:xmpp:avatar:data", item, options);
|
return publish("urn:xmpp:avatar:data", item, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket publishElement(final String namespace, final Element element, final Bundle options) {
|
|
||||||
return publishElement(namespace, element, "curent", options);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IqPacket publishElement(final String namespace, final Element element, String id, final Bundle options) {
|
public IqPacket publishElement(final String namespace, final Element element, String id, final Bundle options) {
|
||||||
final Element item = new Element("item");
|
final Element item = new Element("item");
|
||||||
item.setAttribute("id", id);
|
item.setAttribute("id", id);
|
||||||
|
@ -263,17 +259,17 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
final Element signedPreKeyPublic = bundle.addChild("signedPreKeyPublic");
|
final Element signedPreKeyPublic = bundle.addChild("signedPreKeyPublic");
|
||||||
signedPreKeyPublic.setAttribute("signedPreKeyId", signedPreKeyRecord.getId());
|
signedPreKeyPublic.setAttribute("signedPreKeyId", signedPreKeyRecord.getId());
|
||||||
ECPublicKey publicKey = signedPreKeyRecord.getKeyPair().getPublicKey();
|
ECPublicKey publicKey = signedPreKeyRecord.getKeyPair().getPublicKey();
|
||||||
signedPreKeyPublic.setContent(Base64.encodeToString(publicKey.serialize(), Base64.DEFAULT));
|
signedPreKeyPublic.setContent(Base64.encodeToString(publicKey.serialize(), Base64.NO_WRAP));
|
||||||
final Element signedPreKeySignature = bundle.addChild("signedPreKeySignature");
|
final Element signedPreKeySignature = bundle.addChild("signedPreKeySignature");
|
||||||
signedPreKeySignature.setContent(Base64.encodeToString(signedPreKeyRecord.getSignature(), Base64.DEFAULT));
|
signedPreKeySignature.setContent(Base64.encodeToString(signedPreKeyRecord.getSignature(), Base64.NO_WRAP));
|
||||||
final Element identityKeyElement = bundle.addChild("identityKey");
|
final Element identityKeyElement = bundle.addChild("identityKey");
|
||||||
identityKeyElement.setContent(Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT));
|
identityKeyElement.setContent(Base64.encodeToString(identityKey.serialize(), Base64.NO_WRAP));
|
||||||
|
|
||||||
final Element prekeys = bundle.addChild("prekeys", AxolotlService.PEP_PREFIX);
|
final Element prekeys = bundle.addChild("prekeys", AxolotlService.PEP_PREFIX);
|
||||||
for (PreKeyRecord preKeyRecord : preKeyRecords) {
|
for (PreKeyRecord preKeyRecord : preKeyRecords) {
|
||||||
final Element prekey = prekeys.addChild("preKeyPublic");
|
final Element prekey = prekeys.addChild("preKeyPublic");
|
||||||
prekey.setAttribute("preKeyId", preKeyRecord.getId());
|
prekey.setAttribute("preKeyId", preKeyRecord.getId());
|
||||||
prekey.setContent(Base64.encodeToString(preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.DEFAULT));
|
prekey.setContent(Base64.encodeToString(preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.NO_WRAP));
|
||||||
}
|
}
|
||||||
|
|
||||||
return publish(AxolotlService.PEP_BUNDLES + ":" + deviceId, item, publishOptions);
|
return publish(AxolotlService.PEP_BUNDLES + ":" + deviceId, item, publishOptions);
|
||||||
|
@ -287,13 +283,13 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
for (int i = 0; i < certificates.length; ++i) {
|
for (int i = 0; i < certificates.length; ++i) {
|
||||||
try {
|
try {
|
||||||
Element certificate = chain.addChild("certificate");
|
Element certificate = chain.addChild("certificate");
|
||||||
certificate.setContent(Base64.encodeToString(certificates[i].getEncoded(), Base64.DEFAULT));
|
certificate.setContent(Base64.encodeToString(certificates[i].getEncoded(), Base64.NO_WRAP));
|
||||||
certificate.setAttribute("index", i);
|
certificate.setAttribute("index", i);
|
||||||
} catch (CertificateEncodingException e) {
|
} catch (CertificateEncodingException e) {
|
||||||
Log.d(Config.LOGTAG, "could not encode certificate");
|
Log.d(Config.LOGTAG, "could not encode certificate");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
verification.addChild("signature").setContent(Base64.encodeToString(signature, Base64.DEFAULT));
|
verification.addChild("signature").setContent(Base64.encodeToString(signature, Base64.NO_WRAP));
|
||||||
return publish(AxolotlService.PEP_VERIFICATION + ":" + deviceId, item);
|
return publish(AxolotlService.PEP_VERIFICATION + ":" + deviceId, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,7 @@ import eu.siacs.conversations.entities.ServiceDiscoveryResult;
|
||||||
import eu.siacs.conversations.services.QuickConversationsService;
|
import eu.siacs.conversations.services.QuickConversationsService;
|
||||||
import eu.siacs.conversations.services.ShortcutService;
|
import eu.siacs.conversations.services.ShortcutService;
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
|
import eu.siacs.conversations.utils.CursorUtils;
|
||||||
import eu.siacs.conversations.utils.FtsUtils;
|
import eu.siacs.conversations.utils.FtsUtils;
|
||||||
import eu.siacs.conversations.utils.MimeUtils;
|
import eu.siacs.conversations.utils.MimeUtils;
|
||||||
import eu.siacs.conversations.xmpp.InvalidJid;
|
import eu.siacs.conversations.xmpp.InvalidJid;
|
||||||
|
@ -61,7 +62,7 @@ import rocks.xmpp.addr.Jid;
|
||||||
public class DatabaseBackend extends SQLiteOpenHelper {
|
public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
|
|
||||||
private static final String DATABASE_NAME = "history";
|
private static final String DATABASE_NAME = "history";
|
||||||
private static final int DATABASE_VERSION = 45;
|
private static final int DATABASE_VERSION = 46;
|
||||||
private static DatabaseBackend instance = null;
|
private static DatabaseBackend instance = null;
|
||||||
private static String CREATE_CONTATCS_STATEMENT = "create table "
|
private static String CREATE_CONTATCS_STATEMENT = "create table "
|
||||||
+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
|
+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
|
||||||
|
@ -180,7 +181,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
@Override
|
@Override
|
||||||
public void onConfigure(SQLiteDatabase db) {
|
public void onConfigure(SQLiteDatabase db) {
|
||||||
db.execSQL("PRAGMA foreign_keys=ON");
|
db.execSQL("PRAGMA foreign_keys=ON");
|
||||||
db.rawQuery("PRAGMA secure_delete=ON", null);
|
db.rawQuery("PRAGMA secure_delete=ON", null).close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -527,6 +528,15 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.BODY_LANGUAGE);
|
db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.BODY_LANGUAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldVersion < 46 && newVersion >= 46) {
|
||||||
|
final long start = SystemClock.elapsedRealtime();
|
||||||
|
db.rawQuery("PRAGMA secure_delete = FALSE", null).close();
|
||||||
|
db.execSQL("update "+Message.TABLENAME+" set "+Message.EDITED+"=NULL");
|
||||||
|
db.rawQuery("PRAGMA secure_delete=ON", null).close();
|
||||||
|
final long diff = SystemClock.elapsedRealtime() - start;
|
||||||
|
Log.d(Config.LOGTAG,"deleted old edit information in "+diff+"ms");
|
||||||
|
}
|
||||||
|
|
||||||
db.execSQL("DROP TABLE IF EXISTS resolver_results");
|
db.execSQL("DROP TABLE IF EXISTS resolver_results");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -562,14 +572,14 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
String newJid;
|
String newJid;
|
||||||
try {
|
try {
|
||||||
newJid = Jid.of(cursor.getString(cursor.getColumnIndex(Contact.JID))).toString();
|
newJid = Jid.of(cursor.getString(cursor.getColumnIndex(Contact.JID))).toString();
|
||||||
} catch (IllegalArgumentException ignored) {
|
} catch (final IllegalArgumentException e) {
|
||||||
Log.e(Config.LOGTAG, "Failed to migrate Contact JID "
|
Log.e(Config.LOGTAG, "Failed to migrate Contact JID "
|
||||||
+ cursor.getString(cursor.getColumnIndex(Contact.JID))
|
+ cursor.getString(cursor.getColumnIndex(Contact.JID))
|
||||||
+ ": " + ignored + ". Skipping...");
|
+ ": Skipping...", e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
String updateArgs[] = {
|
final String[] updateArgs = {
|
||||||
newJid,
|
newJid,
|
||||||
cursor.getString(cursor.getColumnIndex(Contact.ACCOUNT)),
|
cursor.getString(cursor.getColumnIndex(Contact.ACCOUNT)),
|
||||||
cursor.getString(cursor.getColumnIndex(Contact.JID)),
|
cursor.getString(cursor.getColumnIndex(Contact.JID)),
|
||||||
|
@ -709,12 +719,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
null, null, Message.TIME_SENT + " DESC",
|
null, null, Message.TIME_SENT + " DESC",
|
||||||
String.valueOf(limit));
|
String.valueOf(limit));
|
||||||
}
|
}
|
||||||
|
CursorUtils.upgradeCursorWindowSize(cursor);
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
try {
|
try {
|
||||||
final Message message = Message.fromCursor(cursor, conversation);
|
list.add(0, Message.fromCursor(cursor, conversation));
|
||||||
if (message != null) {
|
|
||||||
list.add(0, message);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(Config.LOGTAG,"unable to restore message");
|
Log.e(Config.LOGTAG,"unable to restore message");
|
||||||
}
|
}
|
||||||
|
|
|
@ -179,7 +179,7 @@ public class MemorizingTrustManager {
|
||||||
File dir = app.getDir(KEYSTORE_DIR, Context.MODE_PRIVATE);
|
File dir = app.getDir(KEYSTORE_DIR, Context.MODE_PRIVATE);
|
||||||
keyStoreFile = new File(dir + File.separator + KEYSTORE_FILE);
|
keyStoreFile = new File(dir + File.separator + KEYSTORE_FILE);
|
||||||
|
|
||||||
poshCacheDir = app.getFilesDir().getAbsolutePath()+"/posh_cache/";
|
poshCacheDir = app.getCacheDir().getAbsolutePath()+"/posh_cache/";
|
||||||
|
|
||||||
appKeyStore = loadAppKeyStore();
|
appKeyStore = loadAppKeyStore();
|
||||||
}
|
}
|
||||||
|
@ -427,14 +427,18 @@ public class MemorizingTrustManager {
|
||||||
} catch (CertificateException e) {
|
} catch (CertificateException e) {
|
||||||
boolean trustSystemCAs = !PreferenceManager.getDefaultSharedPreferences(master).getBoolean("dont_trust_system_cas", false);
|
boolean trustSystemCAs = !PreferenceManager.getDefaultSharedPreferences(master).getBoolean("dont_trust_system_cas", false);
|
||||||
if (domain != null && isServer && trustSystemCAs && !isIp(domain)) {
|
if (domain != null && isServer && trustSystemCAs && !isIp(domain)) {
|
||||||
String hash = getBase64Hash(chain[0],"SHA-256");
|
final String hash = getBase64Hash(chain[0],"SHA-256");
|
||||||
List<String> fingerprints = getPoshFingerprints(domain);
|
final List<String> fingerprints = getPoshFingerprints(domain);
|
||||||
if (hash != null && fingerprints.contains(hash)) {
|
if (hash != null && fingerprints.size() > 0) {
|
||||||
|
if (fingerprints.contains(hash)) {
|
||||||
Log.d("mtm","trusted cert fingerprint of "+domain+" via posh");
|
Log.d("mtm","trusted cert fingerprint of "+domain+" via posh");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (getPoshCacheFile(domain).delete()) {
|
||||||
|
Log.d("mtm", "deleted posh file for "+domain+" after not being able to verify");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
e.printStackTrace();
|
|
||||||
if (interactive) {
|
if (interactive) {
|
||||||
interactCert(chain, authType, e);
|
interactCert(chain, authType, e);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -15,7 +15,7 @@ import eu.siacs.conversations.Config;
|
||||||
|
|
||||||
public class KnownHostsAdapter extends ArrayAdapter<String> {
|
public class KnownHostsAdapter extends ArrayAdapter<String> {
|
||||||
|
|
||||||
private static Pattern E164_PATTERN = Pattern.compile("^\\+?[1-9]\\d{1,14}$");
|
private static Pattern E164_PATTERN = Pattern.compile("^\\+[1-9]\\d{1,14}$");
|
||||||
|
|
||||||
private ArrayList<String> domains;
|
private ArrayList<String> domains;
|
||||||
private Filter domainFilter = new Filter() {
|
private Filter domainFilter = new Filter() {
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package eu.siacs.conversations.utils;
|
||||||
|
|
||||||
|
import android.database.AbstractWindowedCursor;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.CursorWindow;
|
||||||
|
import android.database.sqlite.SQLiteCursor;
|
||||||
|
|
||||||
|
public class CursorUtils {
|
||||||
|
|
||||||
|
public static void upgradeCursorWindowSize(final Cursor cursor) {
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
|
||||||
|
if (cursor instanceof AbstractWindowedCursor) {
|
||||||
|
final AbstractWindowedCursor windowedCursor = (AbstractWindowedCursor) cursor;
|
||||||
|
windowedCursor.setWindow(new CursorWindow("4M", 4 * 1024 * 1024));
|
||||||
|
}
|
||||||
|
if (cursor instanceof SQLiteCursor) {
|
||||||
|
((SQLiteCursor) cursor).setFillWindowForwardOnly(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -95,7 +95,11 @@ public class JingleConnection implements Transferable {
|
||||||
|
|
||||||
private OnIqPacketReceived responseListener = (account, packet) -> {
|
private OnIqPacketReceived responseListener = (account, packet) -> {
|
||||||
if (packet.getType() != IqPacket.TYPE.RESULT) {
|
if (packet.getType() != IqPacket.TYPE.RESULT) {
|
||||||
|
if (mJingleStatus != JINGLE_STATUS_FAILED && mJingleStatus != JINGLE_STATUS_FINISHED) {
|
||||||
fail(IqParser.extractErrorMessage(packet));
|
fail(IqParser.extractErrorMessage(packet));
|
||||||
|
} else {
|
||||||
|
Log.d(Config.LOGTAG,"ignoring late delivery of jingle packet to jingle session with status="+mJingleStatus+": "+packet.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private byte[] expectedHash = new byte[0];
|
private byte[] expectedHash = new byte[0];
|
||||||
|
@ -674,7 +678,7 @@ public class JingleConnection implements Transferable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendAcceptIbb() {
|
private void sendAcceptIbb() {
|
||||||
this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize);
|
this.transport = new JingleInBandTransport(this, this.transportId, this.ibbBlockSize);
|
||||||
final JinglePacket packet = bootstrapPacket("session-accept");
|
final JinglePacket packet = bootstrapPacket("session-accept");
|
||||||
final Content content = new Content(contentCreator, contentName);
|
final Content content = new Content(contentCreator, contentName);
|
||||||
content.setFileOffer(fileOffer, ftVersion);
|
content.setFileOffer(fileOffer, ftVersion);
|
||||||
|
@ -734,7 +738,7 @@ public class JingleConnection implements Transferable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
respondToIq(packet, true);
|
respondToIq(packet, true);
|
||||||
this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize);
|
this.transport = new JingleInBandTransport(this, this.transportId, this.ibbBlockSize);
|
||||||
this.transport.connect(onIbbTransportConnected);
|
this.transport.connect(onIbbTransportConnected);
|
||||||
} else {
|
} else {
|
||||||
respondToIq(packet, false);
|
respondToIq(packet, false);
|
||||||
|
@ -943,7 +947,7 @@ public class JingleConnection implements Transferable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.transportId = packet.getJingleContent().getTransportId();
|
this.transportId = packet.getJingleContent().getTransportId();
|
||||||
this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize);
|
this.transport = new JingleInBandTransport(this, this.transportId, this.ibbBlockSize);
|
||||||
|
|
||||||
final JinglePacket answer = bootstrapPacket("transport-accept");
|
final JinglePacket answer = bootstrapPacket("transport-accept");
|
||||||
|
|
||||||
|
@ -994,7 +998,7 @@ public class JingleConnection implements Transferable {
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to parse block size in transport-accept");
|
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to parse block size in transport-accept");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize);
|
this.transport = new JingleInBandTransport(this, this.transportId, this.ibbBlockSize);
|
||||||
|
|
||||||
if (sid == null || !sid.equals(this.transportId)) {
|
if (sid == null || !sid.equals(this.transportId)) {
|
||||||
Log.w(Config.LOGTAG, String.format("%s: sid in transport-accept (%s) did not match our sid (%s) ", account.getJid().asBareJid(), sid, transportId));
|
Log.w(Config.LOGTAG, String.format("%s: sid in transport-accept (%s) did not match our sid (%s) ", account.getJid().asBareJid(), sid, transportId));
|
||||||
|
@ -1015,7 +1019,7 @@ public class JingleConnection implements Transferable {
|
||||||
this.mJingleStatus = JINGLE_STATUS_FINISHED;
|
this.mJingleStatus = JINGLE_STATUS_FINISHED;
|
||||||
this.mXmppConnectionService.markMessage(this.message, Message.STATUS_SEND_RECEIVED);
|
this.mXmppConnectionService.markMessage(this.message, Message.STATUS_SEND_RECEIVED);
|
||||||
this.disconnectSocks5Connections();
|
this.disconnectSocks5Connections();
|
||||||
if (this.transport instanceof JingleInbandTransport) {
|
if (this.transport instanceof JingleInBandTransport) {
|
||||||
this.transport.disconnect();
|
this.transport.disconnect();
|
||||||
}
|
}
|
||||||
this.message.setTransferable(null);
|
this.message.setTransferable(null);
|
||||||
|
@ -1033,7 +1037,7 @@ public class JingleConnection implements Transferable {
|
||||||
|
|
||||||
void abort(final String reason) {
|
void abort(final String reason) {
|
||||||
this.disconnectSocks5Connections();
|
this.disconnectSocks5Connections();
|
||||||
if (this.transport instanceof JingleInbandTransport) {
|
if (this.transport instanceof JingleInBandTransport) {
|
||||||
this.transport.disconnect();
|
this.transport.disconnect();
|
||||||
}
|
}
|
||||||
sendSessionTerminate(reason);
|
sendSessionTerminate(reason);
|
||||||
|
@ -1057,7 +1061,7 @@ public class JingleConnection implements Transferable {
|
||||||
private void fail(String errorMessage) {
|
private void fail(String errorMessage) {
|
||||||
this.mJingleStatus = JINGLE_STATUS_FAILED;
|
this.mJingleStatus = JINGLE_STATUS_FAILED;
|
||||||
this.disconnectSocks5Connections();
|
this.disconnectSocks5Connections();
|
||||||
if (this.transport instanceof JingleInbandTransport) {
|
if (this.transport instanceof JingleInBandTransport) {
|
||||||
this.transport.disconnect();
|
this.transport.disconnect();
|
||||||
}
|
}
|
||||||
FileBackend.close(mFileInputStream);
|
FileBackend.close(mFileInputStream);
|
||||||
|
|
|
@ -49,6 +49,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Log.d(Config.LOGTAG, "unable to route jingle packet: " + packet);
|
||||||
IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR);
|
IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR);
|
||||||
Element error = response.addChild("error");
|
Element error = response.addChild("error");
|
||||||
error.setAttribute("type", "cancel");
|
error.setAttribute("type", "cancel");
|
||||||
|
@ -150,17 +151,16 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
if (connection.getAccount() == account
|
if (connection.getAccount() == account
|
||||||
&& connection.hasTransportId(sid)) {
|
&& connection.hasTransportId(sid)) {
|
||||||
JingleTransport transport = connection.getTransport();
|
JingleTransport transport = connection.getTransport();
|
||||||
if (transport instanceof JingleInbandTransport) {
|
if (transport instanceof JingleInBandTransport) {
|
||||||
JingleInbandTransport inbandTransport = (JingleInbandTransport) transport;
|
JingleInBandTransport inbandTransport = (JingleInBandTransport) transport;
|
||||||
inbandTransport.deliverPayload(packet, payload);
|
inbandTransport.deliverPayload(packet, payload);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Log.d(Config.LOGTAG,"couldn't deliver payload: " + payload.toString());
|
|
||||||
} else {
|
|
||||||
Log.d(Config.LOGTAG, "no sid found in incoming ibb packet");
|
|
||||||
}
|
}
|
||||||
|
Log.d(Config.LOGTAG, "unable to deliver ibb packet: " + packet.toString());
|
||||||
|
account.getXmppConnection().sendIqPacket(packet.generateResponse(IqPacket.TYPE.ERROR), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancelInTransmission() {
|
public void cancelInTransmission() {
|
||||||
|
|
|
@ -15,26 +15,25 @@ import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.entities.DownloadableFile;
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
import eu.siacs.conversations.persistance.FileBackend;
|
import eu.siacs.conversations.persistance.FileBackend;
|
||||||
import eu.siacs.conversations.services.AbstractConnectionManager;
|
import eu.siacs.conversations.services.AbstractConnectionManager;
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
||||||
import rocks.xmpp.addr.Jid;
|
import rocks.xmpp.addr.Jid;
|
||||||
|
|
||||||
public class JingleInbandTransport extends JingleTransport {
|
public class JingleInBandTransport extends JingleTransport {
|
||||||
|
|
||||||
private Account account;
|
private final Account account;
|
||||||
private Jid counterpart;
|
private final Jid counterpart;
|
||||||
private int blockSize;
|
private final int blockSize;
|
||||||
private int seq = 0;
|
private int seq = 0;
|
||||||
private String sessionId;
|
private final String sessionId;
|
||||||
|
|
||||||
private boolean established = false;
|
private boolean established = false;
|
||||||
|
|
||||||
private boolean connected = true;
|
private boolean connected = true;
|
||||||
|
|
||||||
private DownloadableFile file;
|
private DownloadableFile file;
|
||||||
private JingleConnection connection;
|
private final JingleConnection connection;
|
||||||
|
|
||||||
private InputStream fileInputStream = null;
|
private InputStream fileInputStream = null;
|
||||||
private InputStream innerInputStream = null;
|
private InputStream innerInputStream = null;
|
||||||
|
@ -61,15 +60,16 @@ public class JingleInbandTransport extends JingleTransport {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public JingleInbandTransport(final JingleConnection connection, final String sid, final int blocksize) {
|
JingleInBandTransport(final JingleConnection connection, final String sid, final int blockSize) {
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
this.account = connection.getAccount();
|
this.account = connection.getAccount();
|
||||||
this.counterpart = connection.getCounterPart();
|
this.counterpart = connection.getCounterPart();
|
||||||
this.blockSize = blocksize;
|
this.blockSize = blockSize;
|
||||||
this.sessionId = sid;
|
this.sessionId = sid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendClose() {
|
private void sendClose() {
|
||||||
|
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": sending ibb close");
|
||||||
IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
|
IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
|
||||||
iq.setTo(this.counterpart);
|
iq.setTo(this.counterpart);
|
||||||
Element close = iq.addChild("close", "http://jabber.org/protocol/ibb");
|
Element close = iq.addChild("close", "http://jabber.org/protocol/ibb");
|
||||||
|
@ -178,9 +178,9 @@ public class JingleInbandTransport extends JingleTransport {
|
||||||
this.seq++;
|
this.seq++;
|
||||||
connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
|
connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
|
||||||
if (this.remainingSize <= 0) {
|
if (this.remainingSize <= 0) {
|
||||||
sendClose();
|
|
||||||
file.setSha1Sum(digest.digest());
|
file.setSha1Sum(digest.digest());
|
||||||
this.onFileTransmissionStatusChanged.onFileTransmitted(file);
|
this.onFileTransmissionStatusChanged.onFileTransmitted(file);
|
||||||
|
sendClose();
|
||||||
fileInputStream.close();
|
fileInputStream.close();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -200,11 +200,7 @@ public class JingleInbandTransport extends JingleTransport {
|
||||||
this.fileOutputStream.write(buffer);
|
this.fileOutputStream.write(buffer);
|
||||||
this.digest.update(buffer);
|
this.digest.update(buffer);
|
||||||
if (this.remainingSize <= 0) {
|
if (this.remainingSize <= 0) {
|
||||||
file.setSha1Sum(digest.digest());
|
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received last block. waiting for close");
|
||||||
fileOutputStream.flush();
|
|
||||||
fileOutputStream.close();
|
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": receive next block nothing remaining");
|
|
||||||
this.onFileTransmissionStatusChanged.onFileTransmitted(file);
|
|
||||||
} else {
|
} else {
|
||||||
connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
|
connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
|
||||||
}
|
}
|
||||||
|
@ -215,7 +211,20 @@ public class JingleInbandTransport extends JingleTransport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deliverPayload(IqPacket packet, Element payload) {
|
private void done() {
|
||||||
|
try {
|
||||||
|
file.setSha1Sum(digest.digest());
|
||||||
|
fileOutputStream.flush();
|
||||||
|
fileOutputStream.close();
|
||||||
|
this.onFileTransmissionStatusChanged.onFileTransmitted(file);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": " + e.getMessage());
|
||||||
|
FileBackend.close(fileOutputStream);
|
||||||
|
this.onFileTransmissionStatusChanged.onFileTransferAborted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void deliverPayload(IqPacket packet, Element payload) {
|
||||||
if (payload.getName().equals("open")) {
|
if (payload.getName().equals("open")) {
|
||||||
if (!established) {
|
if (!established) {
|
||||||
established = true;
|
established = true;
|
||||||
|
@ -235,10 +244,16 @@ public class JingleInbandTransport extends JingleTransport {
|
||||||
this.connected = false;
|
this.connected = false;
|
||||||
this.account.getXmppConnection().sendIqPacket(
|
this.account.getXmppConnection().sendIqPacket(
|
||||||
packet.generateResponse(IqPacket.TYPE.RESULT), null);
|
packet.generateResponse(IqPacket.TYPE.RESULT), null);
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received ibb close");
|
if (this.remainingSize <= 0) {
|
||||||
|
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received ibb close. done");
|
||||||
|
done();
|
||||||
} else {
|
} else {
|
||||||
Log.d(Config.LOGTAG, payload.toString());
|
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received ibb close with " + this.remainingSize + " remaining");
|
||||||
// TODO some sort of exception
|
FileBackend.close(fileOutputStream);
|
||||||
|
this.onFileTransmissionStatusChanged.onFileTransferAborted();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.account.getXmppConnection().sendIqPacket(packet.generateResponse(IqPacket.TYPE.ERROR), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -124,7 +124,7 @@
|
||||||
<string name="pref_never_send_crash">Nunca enviar informe de erros</string>
|
<string name="pref_never_send_crash">Nunca enviar informe de erros</string>
|
||||||
<string name="pref_never_send_crash_summary">Enviando volcados de pilas axudas ao desenvolvemento de Conversations</string>
|
<string name="pref_never_send_crash_summary">Enviando volcados de pilas axudas ao desenvolvemento de Conversations</string>
|
||||||
<string name="pref_confirm_messages">Confirmación de mensaxes</string>
|
<string name="pref_confirm_messages">Confirmación de mensaxes</string>
|
||||||
<string name="pref_confirm_messages_summary">Permitir aos seus contactos saber si recibiu e leu as súas mensaxes</string>
|
<string name="pref_confirm_messages_summary">Permitir aos seus contactos saber si recibeu e leu as súas mensaxes</string>
|
||||||
<string name="pref_ui_options">Interface</string>
|
<string name="pref_ui_options">Interface</string>
|
||||||
<string name="openpgp_error">OpenKeychain informou de un fallo.</string>
|
<string name="openpgp_error">OpenKeychain informou de un fallo.</string>
|
||||||
<string name="bad_key_for_encryption">Chave incorrecta para cifrar.</string>
|
<string name="bad_key_for_encryption">Chave incorrecta para cifrar.</string>
|
||||||
|
@ -557,7 +557,9 @@
|
||||||
<string name="pref_theme_options">Decorado</string>
|
<string name="pref_theme_options">Decorado</string>
|
||||||
<string name="pref_theme_options_summary">Escolla a gama de cores</string>
|
<string name="pref_theme_options_summary">Escolla a gama de cores</string>
|
||||||
<string name="pref_theme_light">Decorado claro</string>
|
<string name="pref_theme_light">Decorado claro</string>
|
||||||
<string name="pref_theme_dark">Decorado oscuro</string>
|
<string name="pref_theme_dark">Decorado escuro</string>
|
||||||
|
<string name="pref_use_green_background">Fondo verde</string>
|
||||||
|
<string name="pref_use_green_background_summary">Utilizar fondo verde para mensaxes recibidas</string>
|
||||||
<string name="unable_to_connect_to_keychain">Non se puido conectar con OpenKeychain</string>
|
<string name="unable_to_connect_to_keychain">Non se puido conectar con OpenKeychain</string>
|
||||||
<string name="this_device_is_no_longer_in_use">Este dispositivo xa non está en uso</string>
|
<string name="this_device_is_no_longer_in_use">Este dispositivo xa non está en uso</string>
|
||||||
<string name="type_pc">Computadora</string>
|
<string name="type_pc">Computadora</string>
|
||||||
|
@ -689,9 +691,9 @@
|
||||||
<string name="disable_encryption_message">¿Está segura de que quere desactivar o cifrado OMEMO para esta conversación? Isto permitirá que o administrador do seu servidor lea as súas mensaxes, pero pode ser a única forma de comunicarse con persoas que usan clientes obsoletos.</string>
|
<string name="disable_encryption_message">¿Está segura de que quere desactivar o cifrado OMEMO para esta conversación? Isto permitirá que o administrador do seu servidor lea as súas mensaxes, pero pode ser a única forma de comunicarse con persoas que usan clientes obsoletos.</string>
|
||||||
<string name="disable_now">Desactivar agora</string>
|
<string name="disable_now">Desactivar agora</string>
|
||||||
<string name="draft">Borrador:</string>
|
<string name="draft">Borrador:</string>
|
||||||
<string name="pref_omemo_setting">Encriptación OMEMO</string>
|
<string name="pref_omemo_setting">Cifrado OMEMO</string>
|
||||||
<string name="pref_omemo_setting_summary_always">OMEMO sempre se utilizará para charlas individuais e privadas en grupo.</string>
|
<string name="pref_omemo_setting_summary_always">OMEMO sempre se utilizará para charlas individuais e privadas en grupo.</string>
|
||||||
<string name="pref_omemo_setting_summary_default_on">OMEMO utilizarase por defecto para as novas conversacións.</string>
|
<string name="pref_omemo_setting_summary_default_on">OMEMO utilizarase por defecto para as novas conversas.</string>
|
||||||
<string name="pref_omemo_setting_summary_default_off">OMEMO terá que ser activado explícitamente para novas conversacións.</string>
|
<string name="pref_omemo_setting_summary_default_off">OMEMO terá que ser activado explícitamente para novas conversacións.</string>
|
||||||
<string name="create_shortcut">Crear acceso directo</string>
|
<string name="create_shortcut">Crear acceso directo</string>
|
||||||
<string name="pref_font_size">Tamaño da fonte</string>
|
<string name="pref_font_size">Tamaño da fonte</string>
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -119,6 +119,7 @@
|
||||||
<string name="pref_sound">Suono di notifica</string>
|
<string name="pref_sound">Suono di notifica</string>
|
||||||
<string name="pref_sound_summary">Esegui un suono quando arriva un nuovo messaggio</string>
|
<string name="pref_sound_summary">Esegui un suono quando arriva un nuovo messaggio</string>
|
||||||
<string name="pref_notification_grace_period">Periodo di grazia</string>
|
<string name="pref_notification_grace_period">Periodo di grazia</string>
|
||||||
|
<string name="pref_notification_grace_period_summary">Il periodo di tempo in cui le notifiche vengono silenziate dopo aver rilevato attività su uno dei tuoi altri dispositivi.</string>
|
||||||
<string name="pref_advanced_options">Avanzate</string>
|
<string name="pref_advanced_options">Avanzate</string>
|
||||||
<string name="pref_never_send_crash">Non inviare mai segnalazioni di errore</string>
|
<string name="pref_never_send_crash">Non inviare mai segnalazioni di errore</string>
|
||||||
<string name="pref_never_send_crash_summary">Se scegli di inviare una segnalazione dell’errore aiuterai lo sviluppo di Conversations</string>
|
<string name="pref_never_send_crash_summary">Se scegli di inviare una segnalazione dell’errore aiuterai lo sviluppo di Conversations</string>
|
||||||
|
@ -337,8 +338,8 @@
|
||||||
<string name="no_application_found_to_open_file">Nessuna applicazione trovata per aprire il file</string>
|
<string name="no_application_found_to_open_file">Nessuna applicazione trovata per aprire il file</string>
|
||||||
<string name="no_application_found_to_open_link">Nessuna applicazione trovata per aprire il link</string>
|
<string name="no_application_found_to_open_link">Nessuna applicazione trovata per aprire il link</string>
|
||||||
<string name="no_application_found_to_view_contact">Nessuna applicazione trovata per vedere il contatto</string>
|
<string name="no_application_found_to_view_contact">Nessuna applicazione trovata per vedere il contatto</string>
|
||||||
<string name="pref_show_dynamic_tags">Tag dinamici</string>
|
<string name="pref_show_dynamic_tags">Etichette dinamiche</string>
|
||||||
<string name="pref_show_dynamic_tags_summary">Mostra tag in sola lettura sotto i contatti</string>
|
<string name="pref_show_dynamic_tags_summary">Mostra etichette in sola lettura sotto i contatti</string>
|
||||||
<string name="enable_notifications">Attiva le notifiche</string>
|
<string name="enable_notifications">Attiva le notifiche</string>
|
||||||
<string name="no_conference_server_found">Nessun server per chat di gruppo trovato</string>
|
<string name="no_conference_server_found">Nessun server per chat di gruppo trovato</string>
|
||||||
<string name="conference_creation_failed">Creazione della chat di gruppo fallita!</string>
|
<string name="conference_creation_failed">Creazione della chat di gruppo fallita!</string>
|
||||||
|
@ -507,7 +508,9 @@
|
||||||
<string name="notify_never">Notifiche disattivate</string>
|
<string name="notify_never">Notifiche disattivate</string>
|
||||||
<string name="notify_paused">Notifiche in pausa</string>
|
<string name="notify_paused">Notifiche in pausa</string>
|
||||||
<string name="pref_picture_compression">Compressione immagini</string>
|
<string name="pref_picture_compression">Compressione immagini</string>
|
||||||
|
<string name="pref_picture_compression_summary">Suggerimento: usa \"Scegli un file\" invece di \"Scegli un\'immagine\" per inviare singole immagini non compresse a prescindere da questa impostazione.</string>
|
||||||
<string name="always">Sempre</string>
|
<string name="always">Sempre</string>
|
||||||
|
<string name="large_images_only">Solo immagini grandi</string>
|
||||||
<string name="battery_optimizations_enabled">Ottimizzazioni batteria attivate</string>
|
<string name="battery_optimizations_enabled">Ottimizzazioni batteria attivate</string>
|
||||||
<string name="battery_optimizations_enabled_explained">Il tuo dispositivo sta facendo delle ingenti ottimizzazioni della batteria per Conversations che potrebbero portare ritardi alle notifiche o anche perdita di messaggi.\nSi consiglia di disattivarle.</string>
|
<string name="battery_optimizations_enabled_explained">Il tuo dispositivo sta facendo delle ingenti ottimizzazioni della batteria per Conversations che potrebbero portare ritardi alle notifiche o anche perdita di messaggi.\nSi consiglia di disattivarle.</string>
|
||||||
<string name="battery_optimizations_enabled_dialog">Il tuo dispositivo sta facendo delle ingenti ottimizzazioni della batteria per Conversations che potrebbero portare ritardi alle notifiche o anche perdita di messaggi.\n\nTi verrà ora chiesto di disattivarle.</string>
|
<string name="battery_optimizations_enabled_dialog">Il tuo dispositivo sta facendo delle ingenti ottimizzazioni della batteria per Conversations che potrebbero portare ritardi alle notifiche o anche perdita di messaggi.\n\nTi verrà ora chiesto di disattivarle.</string>
|
||||||
|
@ -867,4 +870,12 @@
|
||||||
<string name="please_enter_password">Inserisci la password per questo account</string>
|
<string name="please_enter_password">Inserisci la password per questo account</string>
|
||||||
<string name="unable_to_perform_this_action">Impossibile eseguire questa azione</string>
|
<string name="unable_to_perform_this_action">Impossibile eseguire questa azione</string>
|
||||||
<string name="open_join_dialog">Entra in un canale pubblico...</string>
|
<string name="open_join_dialog">Entra in un canale pubblico...</string>
|
||||||
|
<string name="sharing_application_not_grant_permission">L\'applicazione di condivisione non ha concesso l\'autorizzazione per accedere a questo file.</string>
|
||||||
|
<string name="group_chats_and_channels"><![CDATA[Chat di gruppo e canali]]></string>
|
||||||
|
<string name="jabber_network">jabber.network</string>
|
||||||
|
<string name="local_server">Server locale</string>
|
||||||
|
<string name="pref_channel_discovery_summary">La maggior parte degli utenti dovrebbe scegliere ‘jabber.network’ per migliori suggerimenti dall\'intero ecosistema XMPP pubblico.</string>
|
||||||
|
<string name="pref_channel_discovery">Metodo di scoperta canali</string>
|
||||||
|
<string name="backup">Backup</string>
|
||||||
|
<string name="category_about">Al riguardo</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -508,7 +508,9 @@
|
||||||
<string name="notify_never">Notificações desabilitadas</string>
|
<string name="notify_never">Notificações desabilitadas</string>
|
||||||
<string name="notify_paused">Notificações pausadas</string>
|
<string name="notify_paused">Notificações pausadas</string>
|
||||||
<string name="pref_picture_compression">Compressão de imagem</string>
|
<string name="pref_picture_compression">Compressão de imagem</string>
|
||||||
|
<string name="pref_picture_compression_summary">Dica: Use \'Selecione o arquivo\' em vez de \'Selecionar uma imagem\' para enviar uma cópia da imagem original, sem redução de qualidade.</string>
|
||||||
<string name="always">Sempre</string>
|
<string name="always">Sempre</string>
|
||||||
|
<string name="large_images_only">Apenas imagens grandes</string>
|
||||||
<string name="battery_optimizations_enabled">Otimizações de bateria habilitadas</string>
|
<string name="battery_optimizations_enabled">Otimizações de bateria habilitadas</string>
|
||||||
<string name="battery_optimizations_enabled_explained">O seu dispositivo está aplicando uma otimização intensa de bateria no Conversations, que pode levar a atraso nas notificações ou até mesmo perda de mensagens.\nÉ recomendável desabilitar isso.</string>
|
<string name="battery_optimizations_enabled_explained">O seu dispositivo está aplicando uma otimização intensa de bateria no Conversations, que pode levar a atraso nas notificações ou até mesmo perda de mensagens.\nÉ recomendável desabilitar isso.</string>
|
||||||
<string name="battery_optimizations_enabled_dialog">O seu dispositivo está aplicando uma otimização intensa de bateria no Conversations, que pode levar a atraso nas notificações ou até mesmo perda de mensagens.\nAgora você será solicitado a desabilitá-la.</string>
|
<string name="battery_optimizations_enabled_dialog">O seu dispositivo está aplicando uma otimização intensa de bateria no Conversations, que pode levar a atraso nas notificações ou até mesmo perda de mensagens.\nAgora você será solicitado a desabilitá-la.</string>
|
||||||
|
@ -868,4 +870,11 @@
|
||||||
<string name="unable_to_perform_this_action">Não foi possível executar essa ação</string>
|
<string name="unable_to_perform_this_action">Não foi possível executar essa ação</string>
|
||||||
<string name="open_join_dialog">Entrar no canal público...</string>
|
<string name="open_join_dialog">Entrar no canal público...</string>
|
||||||
<string name="sharing_application_not_grant_permission">O aplicativo de compartilhamento não permitiu o acesso a esse arquivo.</string>
|
<string name="sharing_application_not_grant_permission">O aplicativo de compartilhamento não permitiu o acesso a esse arquivo.</string>
|
||||||
|
<string name="group_chats_and_channels"><![CDATA[Conversas em grupo & Canais]]></string>
|
||||||
|
<string name="jabber_network">jabber.network</string>
|
||||||
|
<string name="local_server">Servidor local</string>
|
||||||
|
<string name="pref_channel_discovery_summary">A maioria dos usuários devem escolher \'jabber.network\' para melhores sugestões de toda rede pública do XMPP</string>
|
||||||
|
<string name="pref_channel_discovery">Método de descoberta de canais</string>
|
||||||
|
<string name="backup">Backup</string>
|
||||||
|
<string name="category_about">Sobre</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
<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>
|
<string name="not_available_in_your_country">Quicksy ist in deinem Land nicht verfügbar.</string>
|
||||||
<string name="unable_to_verify_server_identity">Die Überprüfung der Serveridentität ist nicht möglich.</string>
|
<string name="unable_to_verify_server_identity">Überprüfung der Serveridentität ist nicht möglich.</string>
|
||||||
<string name="unknown_security_error">Unbekannter Sicherheitsfehler.</string>
|
<string name="unknown_security_error">Unbekannter Sicherheitsfehler.</string>
|
||||||
<string name="timeout_while_connecting_to_server">Zeitüberschreitung bei der Verbindung zum Server.</string>
|
<string name="timeout_while_connecting_to_server">Zeitüberschreitung bei der Verbindung zum Server.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in New Issue