aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md6
-rw-r--r--README.md2
-rw-r--r--build.gradle4
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java25
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Contact.java9
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Message.java13
-rw-r--r--src/main/java/eu/siacs/conversations/entities/MucOptions.java141
-rw-r--r--src/main/java/eu/siacs/conversations/parser/IqParser.java6
-rw-r--r--src/main/java/eu/siacs/conversations/parser/MessageParser.java8
-rw-r--r--src/main/java/eu/siacs/conversations/parser/PresenceParser.java9
-rw-r--r--src/main/java/eu/siacs/conversations/persistance/FileBackend.java67
-rw-r--r--src/main/java/eu/siacs/conversations/services/MessageArchiveService.java14
-rw-r--r--src/main/java/eu/siacs/conversations/services/XmppConnectionService.java23
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java2
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConversationActivity.java2
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConversationFragment.java5
-rw-r--r--src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java30
-rw-r--r--src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java6
-rw-r--r--src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java3
-rw-r--r--src/main/java/eu/siacs/conversations/ui/forms/FormBooleanFieldWrapper.java39
-rw-r--r--src/main/java/eu/siacs/conversations/ui/forms/FormFieldFactory.java2
-rw-r--r--src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java29
-rw-r--r--src/main/java/eu/siacs/conversations/ui/forms/FormJidSingleFieldWrapper.java13
-rw-r--r--src/main/java/eu/siacs/conversations/ui/forms/FormTextFieldWrapper.java46
-rw-r--r--src/main/java/eu/siacs/conversations/ui/forms/FormWrapper.java72
-rw-r--r--src/main/java/eu/siacs/conversations/utils/DNSHelper.java19
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java16
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/forms/Data.java6
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/forms/Field.java15
-rw-r--r--src/main/res/values/strings.xml7
-rw-r--r--src/main/res/xml/preferences.xml11
31 files changed, 470 insertions, 180 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fbbc6d17..0407f2d4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
###Changelog
+####Version 1.9.3
+* expert setting that enables host and port configuration
+* expert setting opt-out of bookmark autojoin handling
+* offer to rejoin a conference after server sent unavailable
+* internal rewrites
+
####Version 1.9.2
* prevent startup crash on Sailfish OS
* minor bug fixes
diff --git a/README.md b/README.md
index 443dd0d7..2377fc40 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
Conversations: the very last word in instant messaging
-[![Google Play](http://developer.android.com/images/brand/en_generic_rgb_wo_60.png)](https://play.google.com/store/apps/details?id=eu.siacs.conversations&referrer=utm_source%3Dgithub) [![Amazon App Store](https://images-na.ssl-images-amazon.com/images/G/01/AmazonMobileApps/amazon-apps-store-us-black.png)](http://www.amazon.com/dp/B00WD35AAC/)
+[![Google Play](https://conversations.im/images/en-play-badge.png)](https://play.google.com/store/apps/details?id=eu.siacs.conversations&referrer=utm_source%3Dgithub) [![Amazon App Store](https://images-na.ssl-images-amazon.com/images/G/01/AmazonMobileApps/amazon-apps-store-us-black.png)](http://www.amazon.com/dp/B00WD35AAC/)
![screenshots](https://raw.githubusercontent.com/siacs/Conversations/master/screenshots.png)
diff --git a/build.gradle b/build.gradle
index 70190e56..9722b3e2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -53,8 +53,8 @@ android {
defaultConfig {
minSdkVersion 14
targetSdkVersion 23
- versionCode 121
- versionName "1.9.2"
+ versionCode 122
+ versionName "1.9.3"
project.ext.set(archivesBaseName, archivesBaseName + "-" + versionName);
}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
index 43a90010..949975aa 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
@@ -1,5 +1,6 @@
package eu.siacs.conversations.crypto.axolotl;
+import android.os.Bundle;
import android.security.KeyChain;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -39,6 +40,7 @@ import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.parser.IqParser;
import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
@@ -160,6 +162,20 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
AxolotlAddress axolotlAddress = new AxolotlAddress(bareJid, deviceId);
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building session for remote address: " + axolotlAddress.toString());
IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey();
+ if(Config.X509_VERIFICATION) {
+ X509Certificate certificate = store.getFingerprintCertificate(identityKey.getFingerprint().replaceAll("\\s", ""));
+ if (certificate != null) {
+ Bundle information = CryptoHelper.extractCertificateInformation(certificate);
+ try {
+ final String cn = information.getString("subject_cn");
+ final Jid jid = Jid.fromString(bareJid);
+ Log.d(Config.LOGTAG,"setting common name for "+jid+" to "+cn);
+ account.getRoster().getContact(jid).setCommonName(cn);
+ } catch (final InvalidJidException ignored) {
+ //ignored
+ }
+ }
+ }
this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, identityKey));
}
}
@@ -619,6 +635,15 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
setFingerprintTrust(fingerprint, XmppAxolotlSession.Trust.TRUSTED_X509);
axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]);
fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED);
+ Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]);
+ try {
+ final String cn = information.getString("subject_cn");
+ final Jid jid = Jid.fromString(address.getName());
+ Log.d(Config.LOGTAG,"setting common name for "+jid+" to "+cn);
+ account.getRoster().getContact(jid).setCommonName(cn);
+ } catch (final InvalidJidException ignored) {
+ //ignored
+ }
finishBuildingSessionsFromPEP(address);
return;
} catch (Exception e) {
diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java
index ed773b6e..aba9a809 100644
--- a/src/main/java/eu/siacs/conversations/entities/Contact.java
+++ b/src/main/java/eu/siacs/conversations/entities/Contact.java
@@ -38,6 +38,7 @@ public class Contact implements ListItem, Blockable {
protected String systemName;
protected String serverName;
protected String presenceName;
+ protected String commonName;
protected Jid jid;
protected int subscription = 0;
protected String systemAccount;
@@ -105,8 +106,8 @@ public class Contact implements ListItem, Blockable {
}
public String getDisplayName() {
- if (this.presenceName != null && Config.X509_VERIFICATION) {
- return this.presenceName;
+ if (this.commonName != null && Config.X509_VERIFICATION) {
+ return this.commonName;
} else if (this.systemName != null) {
return this.systemName;
} else if (this.serverName != null) {
@@ -515,6 +516,10 @@ public class Contact implements ListItem, Blockable {
return account.getJid().toBareJid().equals(getJid().toBareJid());
}
+ public void setCommonName(String cn) {
+ this.commonName = cn;
+ }
+
public static class Lastseen {
public long time;
public String presence;
diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java
index c5831e7e..f3d891e8 100644
--- a/src/main/java/eu/siacs/conversations/entities/Message.java
+++ b/src/main/java/eu/siacs/conversations/entities/Message.java
@@ -743,13 +743,20 @@ public class Message extends AbstractEntity {
}
public boolean isValidInSession() {
- int pastEncryption = this.getPreviousEncryption();
- int futureEncryption = this.getNextEncryption();
+ int pastEncryption = getCleanedEncryption(this.getPreviousEncryption());
+ int futureEncryption = getCleanedEncryption(this.getNextEncryption());
boolean inUnencryptedSession = pastEncryption == ENCRYPTION_NONE
|| futureEncryption == ENCRYPTION_NONE
|| pastEncryption != futureEncryption;
- return inUnencryptedSession || this.getEncryption() == pastEncryption;
+ return inUnencryptedSession || getCleanedEncryption(this.getEncryption()) == pastEncryption;
+ }
+
+ private static int getCleanedEncryption(int encryption) {
+ if (encryption == ENCRYPTION_DECRYPTED || encryption == ENCRYPTION_DECRYPTION_FAILED) {
+ return ENCRYPTION_PGP;
+ }
+ return encryption;
}
}
diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java
index 014a4c98..7f4ded11 100644
--- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java
+++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java
@@ -3,7 +3,10 @@ package eu.siacs.conversations.entities;
import android.annotation.SuppressLint;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import eu.siacs.conversations.R;
import eu.siacs.conversations.xmpp.forms.Data;
@@ -64,7 +67,7 @@ public class MucOptions {
PARTICIPANT("participant", R.string.participant,2),
NONE("none", R.string.no_role,0);
- private Role(String string, int resId, int rank) {
+ Role(String string, int resId, int rank) {
this.string = string;
this.resId = resId;
this.rank = rank;
@@ -94,6 +97,7 @@ public class MucOptions {
public static final int ERROR_PASSWORD_REQUIRED = 3;
public static final int ERROR_BANNED = 4;
public static final int ERROR_MEMBERS_ONLY = 5;
+ public static final int ERROR_NO_RESPONSE = 6;
public static final int KICKED_FROM_ROOM = 9;
@@ -236,12 +240,12 @@ public class MucOptions {
}
private Account account;
- private final List<User> users = new ArrayList<>();
+ private final Map<String, User> users = Collections.synchronizedMap(new LinkedHashMap<String, User>());
private List<String> features = new ArrayList<>();
private Data form = new Data();
private Conversation conversation;
private boolean isOnline = false;
- private int error = ERROR_UNKNOWN;
+ private int error = ERROR_NO_RESPONSE;
public OnRenameListener onRenameListener = null;
private User self;
private String subject = null;
@@ -303,40 +307,15 @@ public class MucOptions {
}
public User deleteUser(String name) {
- synchronized (this.users) {
- for (int i = 0; i < users.size(); ++i) {
- if (users.get(i).getName().equals(name)) {
- return users.remove(i);
- }
- }
- }
- return null;
+ return this.users.remove(name);
}
public void addUser(User user) {
- synchronized (this.users) {
- for (int i = 0; i < users.size(); ++i) {
- if (users.get(i).getName().equals(user.getName())) {
- users.set(i, user);
- return;
- }
- }
- users.add(user);
- }
+ this.users.put(user.getName(), user);
}
public User findUser(String name) {
- if (name == null) {
- return null;
- }
- synchronized (this.users) {
- for (User user : users) {
- if (user.getName().equals(name)) {
- return user;
- }
- }
- }
- return null;
+ return this.users.get(name);
}
public boolean isUserInRoom(String name) {
@@ -344,26 +323,34 @@ public class MucOptions {
}
public void setError(int error) {
- this.isOnline = error == ERROR_NO_ERROR;
+ this.isOnline = isOnline && error == ERROR_NO_ERROR;
this.error = error;
}
+ public void setOnline() {
+ this.isOnline = true;
+ }
+
public ArrayList<User> getUsers() {
- synchronized (this.users) {
- return new ArrayList(this.users);
- }
+ return new ArrayList<>(users.values());
}
public List<User> getUsers(int max) {
- synchronized (this.users) {
- return new ArrayList<>(users.subList(0,Math.min(users.size(),5)));
+ ArrayList<User> users = new ArrayList<>();
+ int i = 1;
+ for(User user : this.users.values()) {
+ users.add(user);
+ if (i >= max) {
+ break;
+ } else {
+ ++i;
+ }
}
+ return users;
}
public int getUserCount() {
- synchronized (this.users) {
- return this.users.size();
- }
+ return this.users.size();
}
public String getProposedNick() {
@@ -400,7 +387,7 @@ public class MucOptions {
public void setOffline() {
this.users.clear();
- this.error = 0;
+ this.error = ERROR_NO_RESPONSE;
this.isOnline = false;
}
@@ -417,38 +404,34 @@ public class MucOptions {
}
public String createNameFromParticipants() {
- synchronized (this.users) {
- if (users.size() >= 2) {
- List<String> names = new ArrayList<String>();
- for (User user : users) {
- Contact contact = user.getContact();
- if (contact != null && !contact.getDisplayName().isEmpty()) {
- names.add(contact.getDisplayName().split("\\s+")[0]);
- } else {
- names.add(user.getName());
- }
+ if (users.size() >= 2) {
+ List<String> names = new ArrayList<>();
+ for (User user : getUsers(5)) {
+ Contact contact = user.getContact();
+ if (contact != null && !contact.getDisplayName().isEmpty()) {
+ names.add(contact.getDisplayName().split("\\s+")[0]);
+ } else {
+ names.add(user.getName());
}
- StringBuilder builder = new StringBuilder();
- for (int i = 0; i < names.size(); ++i) {
- builder.append(names.get(i));
- if (i != names.size() - 1) {
- builder.append(", ");
- }
+ }
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < names.size(); ++i) {
+ builder.append(names.get(i));
+ if (i != names.size() - 1) {
+ builder.append(", ");
}
- return builder.toString();
- } else {
- return null;
}
+ return builder.toString();
+ } else {
+ return null;
}
}
public long[] getPgpKeyIds() {
List<Long> ids = new ArrayList<>();
- synchronized (this.users) {
- for (User user : this.users) {
- if (user.getPgpKeyId() != 0) {
- ids.add(user.getPgpKeyId());
- }
+ for (User user : this.users.values()) {
+ if (user.getPgpKeyId() != 0) {
+ ids.add(user.getPgpKeyId());
}
}
ids.add(account.getPgpId());
@@ -460,22 +443,18 @@ public class MucOptions {
}
public boolean pgpKeysInUse() {
- synchronized (this.users) {
- for (User user : this.users) {
- if (user.getPgpKeyId() != 0) {
- return true;
- }
+ for (User user : this.users.values()) {
+ if (user.getPgpKeyId() != 0) {
+ return true;
}
}
return false;
}
public boolean everybodyHasKeys() {
- synchronized (this.users) {
- for (User user : this.users) {
- if (user.getPgpKeyId() == 0) {
- return false;
- }
+ for (User user : this.users.values()) {
+ if (user.getPgpKeyId() == 0) {
+ return false;
}
}
return true;
@@ -489,15 +468,9 @@ public class MucOptions {
}
}
- public Jid getTrueCounterpart(String counterpart) {
- synchronized (this.users) {
- for (User user : this.users) {
- if (user.getName().equals(counterpart)) {
- return user.getJid();
- }
- }
- }
- return null;
+ public Jid getTrueCounterpart(String name) {
+ User user = findUser(name);
+ return user == null ? null : user.getJid();
}
public String getPassword() {
diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java
index 1761e0df..09bbabeb 100644
--- a/src/main/java/eu/siacs/conversations/parser/IqParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java
@@ -142,7 +142,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
}
try {
publicKey = Curve.decodePoint(Base64.decode(signedPreKeyPublic.getContent(),Base64.DEFAULT), 0);
- } catch (InvalidKeyException | IllegalArgumentException e) {
+ } catch (InvalidKeyException | IllegalArgumentException | NullPointerException e) {
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Invalid signedPreKeyPublic in PEP: " + e.getMessage());
}
return publicKey;
@@ -169,7 +169,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
}
try {
identityKey = new IdentityKey(Base64.decode(identityKeyElement.getContent(), Base64.DEFAULT), 0);
- } catch (InvalidKeyException | IllegalArgumentException e) {
+ } catch (InvalidKeyException | IllegalArgumentException | NullPointerException e) {
Log.e(Config.LOGTAG,AxolotlService.LOGPREFIX+" : "+"Invalid identityKey in PEP: "+e.getMessage());
}
return identityKey;
@@ -200,7 +200,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
try {
ECPublicKey preKeyPublic = Curve.decodePoint(Base64.decode(preKeyPublicElement.getContent(), Base64.DEFAULT), 0);
preKeyRecords.put(preKeyId, preKeyPublic);
- } catch (InvalidKeyException | IllegalArgumentException e) {
+ } catch (InvalidKeyException | IllegalArgumentException | NullPointerException e) {
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Invalid preKeyPublic (ID="+preKeyId+") in PEP: "+ e.getMessage()+", skipping...");
continue;
}
diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
index 345e8395..60948918 100644
--- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
@@ -271,7 +271,7 @@ public class MessageParser extends AbstractParser implements
packet = f.first;
isForwarded = true;
serverMsgId = result.getAttribute("id");
- query.incrementTotalCount();
+ query.incrementMessageCount();
} else if (query != null) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": received mam result from invalid sender");
return;
@@ -411,9 +411,7 @@ public class MessageParser extends AbstractParser implements
}
}
- if (query != null) {
- query.incrementMessageCount();
- } else {
+ if (query == null) {
mXmppConnectionService.updateConversationUi();
}
@@ -454,7 +452,7 @@ public class MessageParser extends AbstractParser implements
mXmppConnectionService.getNotificationService().pushFromBacklog(message);
}
}
- } else { //no body
+ } else if (!packet.hasChild("body")){ //no body
if (isTypeGroupChat) {
Conversation conversation = mXmppConnectionService.find(account, from.toBareJid());
if (packet.hasChild("subject")) {
diff --git a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java
index da7a8ce6..5a25b283 100644
--- a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java
@@ -1,8 +1,12 @@
package eu.siacs.conversations.parser;
+import android.util.Log;
+
import java.util.ArrayList;
import java.util.List;
+
+import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.PgpEngine;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
@@ -38,6 +42,7 @@ public class PresenceParser extends AbstractParser implements
processConferencePresence(packet, mucOptions);
final List<MucOptions.User> tileUserAfter = mucOptions.getUsers(5);
if (!tileUserAfter.equals(tileUserBefore)) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": update tiles for "+conversation.getName());
mXmppConnectionService.getAvatarService().clear(mucOptions);
}
if (before != mucOptions.online() || (mucOptions.online() && count != mucOptions.getUserCount())) {
@@ -59,12 +64,13 @@ public class PresenceParser extends AbstractParser implements
if (x != null) {
Element item = x.findChild("item");
if (item != null && !from.isBareJid()) {
+ mucOptions.setError(MucOptions.ERROR_NO_ERROR);
MucOptions.User user = new MucOptions.User(mucOptions,from);
user.setAffiliation(item.getAttribute("affiliation"));
user.setRole(item.getAttribute("role"));
user.setJid(item.getAttributeAsJid("jid"));
if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE) || packet.getFrom().equals(mucOptions.getConversation().getJid())) {
- mucOptions.setError(MucOptions.ERROR_NO_ERROR);
+ mucOptions.setOnline();
mucOptions.setSelf(user);
if (mucOptions.mNickChangingInProgress) {
if (mucOptions.onRenameListener != null) {
@@ -111,6 +117,7 @@ public class PresenceParser extends AbstractParser implements
mucOptions.setError(MucOptions.ERROR_MEMBERS_ONLY);
} else {
mucOptions.setError(MucOptions.ERROR_UNKNOWN);
+ Log.d(Config.LOGTAG, "unknown error in conference: " + packet);
}
} else if (!from.isBareJid()){
MucOptions.User user = mucOptions.deleteUser(from.getResourcepart());
diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
index 05932acc..4bdf080c 100644
--- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
+++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
@@ -86,7 +86,7 @@ public class FileBackend {
public static String getConversationsImageDirectory() {
return Environment.getExternalStoragePublicDirectory(
- Environment.DIRECTORY_PICTURES).getAbsolutePath()
+ Environment.DIRECTORY_PICTURES).getAbsolutePath()
+ "/Conversations/";
}
@@ -155,12 +155,7 @@ public class FileBackend {
return FileUtils.getPath(mXmppConnectionService,uri);
}
- public DownloadableFile copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException {
- Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage");
- String mime = mXmppConnectionService.getContentResolver().getType(uri);
- String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime);
- message.setRelativeFilePath(message.getUuid() + "." + extension);
- DownloadableFile file = mXmppConnectionService.getFileBackend().getFile(message);
+ public void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException {
file.getParentFile().mkdirs();
OutputStream os = null;
InputStream is = null;
@@ -183,28 +178,18 @@ public class FileBackend {
close(os);
close(is);
}
- Log.d(Config.LOGTAG, "output file name " + mXmppConnectionService.getFileBackend().getFile(message));
- return file;
+ Log.d(Config.LOGTAG, "output file name " + file.getAbsolutePath());
}
- public DownloadableFile copyImageToPrivateStorage(Message message, Uri image)
- throws FileCopyException {
- return this.copyImageToPrivateStorage(message, image, 0);
+ public void copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException {
+ Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage");
+ String mime = mXmppConnectionService.getContentResolver().getType(uri);
+ String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime);
+ message.setRelativeFilePath(message.getUuid() + "." + extension);
+ copyFileToPrivateStorage(mXmppConnectionService.getFileBackend().getFile(message), uri);
}
- private DownloadableFile copyImageToPrivateStorage(Message message,Uri image, int sampleSize) throws FileCopyException {
- switch(Config.IMAGE_FORMAT) {
- case JPEG:
- message.setRelativeFilePath(message.getUuid()+".jpg");
- break;
- case PNG:
- message.setRelativeFilePath(message.getUuid()+".png");
- break;
- case WEBP:
- message.setRelativeFilePath(message.getUuid()+".webp");
- break;
- }
- DownloadableFile file = getFile(message);
+ private void copyImageToPrivateStorage(File file, Uri image, int sampleSize) throws FileCopyException {
file.getParentFile().mkdirs();
InputStream is = null;
OutputStream os = null;
@@ -225,7 +210,6 @@ public class FileBackend {
int rotation = getRotation(image);
scaledBitmap = rotate(scaledBitmap, rotation);
boolean targetSizeReached = false;
- long size = 0;
int quality = Config.IMAGE_QUALITY;
while(!targetSizeReached) {
os = new FileOutputStream(file);
@@ -234,14 +218,11 @@ public class FileBackend {
throw new FileCopyException(R.string.error_compressing_image);
}
os.flush();
- size = file.getSize();
- targetSizeReached = size <= Config.IMAGE_MAX_SIZE || quality <= 50;
+ targetSizeReached = file.length() <= Config.IMAGE_MAX_SIZE || quality <= 50;
quality -= 5;
}
- int width = scaledBitmap.getWidth();
- int height = scaledBitmap.getHeight();
- message.setBody(Long.toString(size) + '|' + width + '|' + height);
- return file;
+ scaledBitmap.recycle();
+ return;
} catch (FileNotFoundException e) {
throw new FileCopyException(R.string.error_file_not_found);
} catch (IOException e) {
@@ -252,7 +233,7 @@ public class FileBackend {
} catch (OutOfMemoryError e) {
++sampleSize;
if (sampleSize <= 3) {
- return copyImageToPrivateStorage(message, image, sampleSize);
+ copyImageToPrivateStorage(file, image, sampleSize);
} else {
throw new FileCopyException(R.string.error_out_of_memory);
}
@@ -264,6 +245,26 @@ public class FileBackend {
}
}
+ public void copyImageToPrivateStorage(File file, Uri image) throws FileCopyException {
+ copyImageToPrivateStorage(file, image, 0);
+ }
+
+ public void copyImageToPrivateStorage(Message message, Uri image) throws FileCopyException {
+ switch(Config.IMAGE_FORMAT) {
+ case JPEG:
+ message.setRelativeFilePath(message.getUuid()+".jpg");
+ break;
+ case PNG:
+ message.setRelativeFilePath(message.getUuid()+".png");
+ break;
+ case WEBP:
+ message.setRelativeFilePath(message.getUuid()+".webp");
+ break;
+ }
+ copyImageToPrivateStorage(getFile(message), image);
+ updateFileParams(message);
+ }
+
private int getRotation(File file) {
return getRotation(Uri.parse("file://"+file.getAbsolutePath()));
}
diff --git a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java
index 4403f99c..6fcb4612 100644
--- a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java
+++ b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java
@@ -201,10 +201,10 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
Element last = set == null ? null : set.findChild("last");
Element first = set == null ? null : set.findChild("first");
Element relevant = query.getPagingOrder() == PagingOrder.NORMAL ? last : first;
- boolean abort = (query.getStart() == 0 && query.getTotalCount() >= Config.PAGE_SIZE) || query.getTotalCount() >= Config.MAM_MAX_MESSAGES;
+ boolean abort = (query.getStart() == 0 && query.getMessageCount() >= Config.PAGE_SIZE) || query.getMessageCount() >= Config.MAM_MAX_MESSAGES;
if (complete || relevant == null || abort) {
this.finalizeQuery(query);
- Log.d(Config.LOGTAG,query.getAccount().getJid().toBareJid().toString()+": finished mam after "+query.getTotalCount()+" messages");
+ Log.d(Config.LOGTAG,query.getAccount().getJid().toBareJid().toString()+": finished mam after "+query.getMessageCount()+" messages");
if (query.getWith() == null && query.getMessageCount() > 0) {
mXmppConnectionService.getNotificationService().finishBacklog(true);
}
@@ -246,7 +246,6 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
}
public class Query {
- private int totalCount = 0;
private int messageCount = 0;
private long start;
private long end;
@@ -279,7 +278,6 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
Query query = new Query(this.account,this.start,this.end);
query.reference = reference;
query.conversation = conversation;
- query.totalCount = totalCount;
query.callback = callback;
return query;
}
@@ -345,18 +343,10 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
return this.account;
}
- public void incrementTotalCount() {
- this.totalCount++;
- }
-
public void incrementMessageCount() {
this.messageCount++;
}
- public int getTotalCount() {
- return this.totalCount;
- }
-
public int getMessageCount() {
return this.messageCount;
}
diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
index 9ad8ea1c..43ca5a54 100644
--- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
@@ -335,7 +335,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public PgpEngine getPgpEngine() {
- if (pgpServiceConnection.isBound()) {
+ if (pgpServiceConnection != null && pgpServiceConnection.isBound()) {
if (this.mPgpEngine == null) {
this.mPgpEngine = new PgpEngine(new OpenPgpApi(
getApplicationContext(),
@@ -1002,6 +1002,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
final Element query = packet.query();
final HashMap<Jid, Bookmark> bookmarks = new HashMap<>();
final Element storage = query.findChild("storage", "storage:bookmarks");
+ final boolean autojoin = respectAutojoin();
if (storage != null) {
for (final Element item : storage.getChildren()) {
if (item.getName().equals("conference")) {
@@ -1013,7 +1014,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
Conversation conversation = find(bookmark);
if (conversation != null) {
conversation.setBookmark(bookmark);
- } else if (bookmark.autojoin() && bookmark.getJid() != null) {
+ } else if (bookmark.autojoin() && bookmark.getJid() != null && autojoin) {
conversation = findOrCreateConversation(
account, bookmark.getJid(), true);
conversation.setBookmark(bookmark);
@@ -1331,7 +1332,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (conversation.getMode() == Conversation.MODE_MULTI) {
if (conversation.getAccount().getStatus() == Account.State.ONLINE) {
Bookmark bookmark = conversation.getBookmark();
- if (bookmark != null && bookmark.autojoin()) {
+ if (bookmark != null && bookmark.autojoin() && respectAutojoin()) {
bookmark.setAutojoin(false);
pushBookmarks(bookmark.getAccount());
}
@@ -1792,7 +1793,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (conversation.getMode() == Conversation.MODE_MULTI) {
conversation.getMucOptions().setPassword(password);
if (conversation.getBookmark() != null) {
- conversation.getBookmark().setAutojoin(true);
+ if (respectAutojoin()) {
+ conversation.getBookmark().setAutojoin(true);
+ }
pushBookmarks(conversation.getAccount());
}
databaseBackend.updateConversation(conversation);
@@ -1970,7 +1973,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
}
- Element form = query.findChild("x","jabber:x:data");
+ Element form = query.findChild("x", "jabber:x:data");
if (form != null) {
conversation.getMucOptions().updateFormData(Data.parse(form));
}
@@ -2379,7 +2382,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
updateConversationUi();
updateRosterUi();
} else {
- Conversation conversation = find(account,avatar.owner.toBareJid());
+ Conversation conversation = find(account, avatar.owner.toBareJid());
if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
MucOptions.User user = conversation.getMucOptions().findUser(avatar.owner.getResourcepart());
if (user != null) {
@@ -2579,6 +2582,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return !getPreferences().getBoolean("dont_save_encrypted", false);
}
+ private boolean respectAutojoin() {
+ return getPreferences().getBoolean("autojoin", true);
+ }
+
public boolean indicateReceived() {
return getPreferences().getBoolean("indicate_received", false);
}
@@ -2587,6 +2594,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return Config.FORCE_ORBOT || getPreferences().getBoolean("use_tor", false);
}
+ public boolean showExtendedConnectionOptions() {
+ return getPreferences().getBoolean("show_connection_options", false);
+ }
+
public int unreadCount() {
int count = 0;
for (Conversation conversation : getConversations()) {
diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
index 6653dd24..be454936 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
@@ -468,7 +468,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
bookmark.setNick(mConversation.getJid().getResourcepart());
}
bookmark.setBookmarkName(mConversation.getMucOptions().getSubject());
- bookmark.setAutojoin(true);
+ bookmark.setAutojoin(getPreferences().getBoolean("autojoin",true));
account.getBookmarks().add(bookmark);
xmppConnectionService.pushBookmarks(account);
mConversation.setBookmark(bookmark);
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
index b4857067..2461c39d 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
@@ -854,7 +854,7 @@ public class ConversationActivity extends XmppActivity
MenuItem pgp = popup.getMenu().findItem(R.id.encryption_choice_pgp);
MenuItem axolotl = popup.getMenu().findItem(R.id.encryption_choice_axolotl);
pgp.setVisible(!Config.HIDE_PGP_IN_UI && !Config.X509_VERIFICATION);
- none.setVisible(!Config.FORCE_E2E_ENCRYPTION);
+ none.setVisible(!Config.FORCE_E2E_ENCRYPTION || conversation.getMode() == Conversation.MODE_MULTI);
otr.setVisible(!Config.X509_VERIFICATION);
if (conversation.getMode() == Conversation.MODE_MULTI) {
otr.setVisible(false);
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
index 352d00ca..af6f5d6f 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
@@ -770,7 +770,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
case MucOptions.ERROR_NICK_IN_USE:
showSnackbar(R.string.nick_in_use, R.string.edit, clickToMuc);
break;
- case MucOptions.ERROR_UNKNOWN:
+ case MucOptions.ERROR_NO_RESPONSE:
showSnackbar(R.string.conference_not_found, R.string.leave, leaveMuc);
break;
case MucOptions.ERROR_PASSWORD_REQUIRED:
@@ -785,6 +785,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
case MucOptions.KICKED_FROM_ROOM:
showSnackbar(R.string.conference_kicked, R.string.join, joinMuc);
break;
+ case MucOptions.ERROR_UNKNOWN:
+ showSnackbar(R.string.conference_unknown_error, R.string.try_again, joinMuc);
+ break;
default:
break;
}
diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
index 24490699..5c783f54 100644
--- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
@@ -5,6 +5,7 @@ import android.app.AlertDialog.Builder;
import android.app.PendingIntent;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
@@ -91,7 +92,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
private Jid jidToEdit;
private boolean mInitMode = false;
- private boolean mUseTor = false;
+ private boolean mShowOptions = false;
private Account mAccount;
private String messageFingerprint;
@@ -133,7 +134,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
}
String hostname = null;
int numericPort = 5222;
- if (mUseTor) {
+ if (mShowOptions) {
hostname = mHostname.getText().toString();
final String port = mPort.getText().toString();
if (hostname.contains(" ")) {
@@ -511,8 +512,11 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
}
}
}
- this.mUseTor = Config.FORCE_ORBOT || getPreferences().getBoolean("use_tor", false);
- this.mNamePort.setVisibility(mUseTor ? View.VISIBLE : View.GONE);
+ SharedPreferences preferences = getPreferences();
+ boolean useTor = Config.FORCE_ORBOT || preferences.getBoolean("use_tor", false);
+ this.mShowOptions = useTor || preferences.getBoolean("show_connection_options", false);
+ mHostname.setHint(useTor ? R.string.hostname_or_onion : R.string.hostname_example);
+ this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
}
@Override
@@ -598,7 +602,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
this.mHostname.getEditableText().append(this.mAccount.getHostname());
this.mPort.setText("");
this.mPort.getEditableText().append(String.valueOf(this.mAccount.getPort()));
- this.mNamePort.setVisibility(mUseTor ? View.VISIBLE : View.GONE);
+ this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
}
if (!mInitMode) {
@@ -740,12 +744,24 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
}
} else {
if (this.mAccount.errorStatus()) {
- this.mAccountJid.setError(getString(this.mAccount.getStatus().getReadableId()));
+ final EditText errorTextField;
+ if (this.mAccount.getStatus() == Account.State.UNAUTHORIZED) {
+ errorTextField = this.mPassword;
+ } else if (mShowOptions
+ && this.mAccount.getStatus() == Account.State.SERVER_NOT_FOUND
+ && this.mHostname.getText().length() > 0) {
+ errorTextField = this.mHostname;
+ } else {
+ errorTextField = this.mAccountJid;
+ }
+ errorTextField.setError(getString(this.mAccount.getStatus().getReadableId()));
if (init || !accountInfoEdited()) {
- this.mAccountJid.requestFocus();
+ errorTextField.requestFocus();
}
} else {
this.mAccountJid.setError(null);
+ this.mPassword.setError(null);
+ this.mHostname.setError(null);
}
this.mStats.setVisibility(View.GONE);
}
diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
index 63859337..8b3b4607 100644
--- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
@@ -287,7 +287,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
if (!conversation.getMucOptions().online()) {
xmppConnectionService.joinMuc(conversation);
}
- if (!bookmark.autojoin()) {
+ if (!bookmark.autojoin() && getPreferences().getBoolean("autojoin", true)) {
bookmark.setAutojoin(true);
xmppConnectionService.pushBookmarks(bookmark.getAccount());
}
@@ -427,7 +427,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
jid.setError(getString(R.string.bookmark_already_exists));
} else {
final Bookmark bookmark = new Bookmark(account, conferenceJid.toBareJid());
- bookmark.setAutojoin(true);
+ bookmark.setAutojoin(getPreferences().getBoolean("autojoin", true));
String nick = conferenceJid.getResourcepart();
if (nick != null && !nick.isEmpty()) {
bookmark.setNick(nick);
@@ -806,7 +806,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
final AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
if (mResContextMenu == R.menu.conference_context) {
activity.conference_context_id = acmi.position;
- } else {
+ } else if (mResContextMenu == R.menu.contact_context){
activity.contact_context_id = acmi.position;
final Blockable contact = (Contact) activity.contacts.get(acmi.position);
final MenuItem blockUnblockItem = menu.findItem(R.id.context_contact_block_unblock);
diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java
index 4be4931f..47414f90 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java
@@ -78,9 +78,10 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
}
final Jid jid = item.getJid();
if (jid != null) {
+ tvJid.setVisibility(View.VISIBLE);
tvJid.setText(jid.toString());
} else {
- tvJid.setText("");
+ tvJid.setVisibility(View.GONE);
}
tvName.setText(item.getDisplayName());
loadAvatar(item,picture);
diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormBooleanFieldWrapper.java b/src/main/java/eu/siacs/conversations/ui/forms/FormBooleanFieldWrapper.java
index e9adf15a..6cb357a9 100644
--- a/src/main/java/eu/siacs/conversations/ui/forms/FormBooleanFieldWrapper.java
+++ b/src/main/java/eu/siacs/conversations/ui/forms/FormBooleanFieldWrapper.java
@@ -2,6 +2,7 @@ package eu.siacs.conversations.ui.forms;
import android.content.Context;
import android.widget.CheckBox;
+import android.widget.CompoundButton;
import java.util.ArrayList;
import java.util.List;
@@ -16,6 +17,13 @@ public class FormBooleanFieldWrapper extends FormFieldWrapper {
protected FormBooleanFieldWrapper(Context context, Field field) {
super(context, field);
checkBox = (CheckBox) view.findViewById(R.id.field);
+ checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ checkBox.setError(null);
+ invokeOnFormFieldValuesEdited();
+ }
+ });
}
@Override
@@ -32,12 +40,41 @@ public class FormBooleanFieldWrapper extends FormFieldWrapper {
}
@Override
+ protected void setValues(List<String> values) {
+ if (values.size() == 0) {
+ checkBox.setChecked(false);
+ } else {
+ checkBox.setChecked(Boolean.parseBoolean(values.get(0)));
+ }
+ }
+
+ @Override
public boolean validates() {
- return checkBox.isChecked() || !field.isRequired();
+ if (checkBox.isChecked() || !field.isRequired()) {
+ return true;
+ } else {
+ checkBox.setError(context.getString(R.string.this_field_is_required));
+ checkBox.requestFocus();
+ return false;
+ }
+ }
+
+ @Override
+ public boolean edited() {
+ if (field.getValues().size() == 0) {
+ return checkBox.isChecked();
+ } else {
+ return super.edited();
+ }
}
@Override
protected int getLayoutResource() {
return R.layout.form_boolean;
}
+
+ @Override
+ void setReadOnly(boolean readOnly) {
+ checkBox.setEnabled(!readOnly);
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormFieldFactory.java b/src/main/java/eu/siacs/conversations/ui/forms/FormFieldFactory.java
index 966c0e5a..ee306472 100644
--- a/src/main/java/eu/siacs/conversations/ui/forms/FormFieldFactory.java
+++ b/src/main/java/eu/siacs/conversations/ui/forms/FormFieldFactory.java
@@ -20,7 +20,7 @@ public class FormFieldFactory {
typeTable.put("boolean", FormBooleanFieldWrapper.class);
}
- public static FormFieldWrapper createFromField(Context context, Field field) {
+ protected static FormFieldWrapper createFromField(Context context, Field field) {
Class clazz = typeTable.get(field.getType());
if (clazz == null) {
clazz = FormTextFieldWrapper.class;
diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java b/src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java
index f2560ef5..3a21ade3 100644
--- a/src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java
+++ b/src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java
@@ -4,11 +4,13 @@ import android.content.Context;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import java.util.List;
+import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.xmpp.forms.Field;
@@ -17,6 +19,7 @@ public abstract class FormFieldWrapper {
protected final Context context;
protected final Field field;
protected final View view;
+ protected OnFormFieldValuesEdited onFormFieldValuesEditedListener;
protected FormFieldWrapper(Context context, Field field) {
this.context = context;
@@ -42,10 +45,14 @@ public abstract class FormFieldWrapper {
abstract List<String> getValues();
+ protected abstract void setValues(List<String> values);
+
abstract boolean validates();
abstract protected int getLayoutResource();
+ abstract void setReadOnly(boolean readOnly);
+
protected SpannableString createSpannableLabelString(String label, boolean required) {
SpannableString spannableString = new SpannableString(label + (required ? " *" : ""));
if (required) {
@@ -57,12 +64,32 @@ public abstract class FormFieldWrapper {
return spannableString;
}
+ protected void invokeOnFormFieldValuesEdited() {
+ if (this.onFormFieldValuesEditedListener != null) {
+ this.onFormFieldValuesEditedListener.onFormFieldValuesEdited();
+ }
+ }
+
+ public boolean edited() {
+ return !field.getValues().equals(getValues());
+ }
+
+ public void setOnFormFieldValuesEditedListener(OnFormFieldValuesEdited listener) {
+ this.onFormFieldValuesEditedListener = listener;
+ }
+
protected static <F extends FormFieldWrapper> FormFieldWrapper createFromField(Class<F> c, Context context, Field field) {
try {
- return c.getDeclaredConstructor(Context.class, Field.class).newInstance(context,field);
+ F fieldWrapper = c.getDeclaredConstructor(Context.class, Field.class).newInstance(context,field);
+ fieldWrapper.setValues(field.getValues());
+ return fieldWrapper;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
+
+ public interface OnFormFieldValuesEdited {
+ void onFormFieldValuesEdited();
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormJidSingleFieldWrapper.java b/src/main/java/eu/siacs/conversations/ui/forms/FormJidSingleFieldWrapper.java
index b54940f6..553e8f21 100644
--- a/src/main/java/eu/siacs/conversations/ui/forms/FormJidSingleFieldWrapper.java
+++ b/src/main/java/eu/siacs/conversations/ui/forms/FormJidSingleFieldWrapper.java
@@ -3,6 +3,8 @@ package eu.siacs.conversations.ui.forms;
import android.content.Context;
import android.text.InputType;
+import java.util.List;
+
import eu.siacs.conversations.R;
import eu.siacs.conversations.xmpp.forms.Field;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
@@ -23,9 +25,20 @@ public class FormJidSingleFieldWrapper extends FormTextFieldWrapper {
try {
Jid.fromString(value);
} catch (InvalidJidException e) {
+ editText.setError(context.getString(R.string.invalid_jid));
+ editText.requestFocus();
return false;
}
}
return super.validates();
}
+
+ @Override
+ protected void setValues(List<String> values) {
+ StringBuilder builder = new StringBuilder("");
+ for(String value : values) {
+ builder.append(value);
+ }
+ editText.setText(builder.toString());
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormTextFieldWrapper.java b/src/main/java/eu/siacs/conversations/ui/forms/FormTextFieldWrapper.java
index 274936e8..b7dac951 100644
--- a/src/main/java/eu/siacs/conversations/ui/forms/FormTextFieldWrapper.java
+++ b/src/main/java/eu/siacs/conversations/ui/forms/FormTextFieldWrapper.java
@@ -1,7 +1,9 @@
package eu.siacs.conversations.ui.forms;
import android.content.Context;
+import android.text.Editable;
import android.text.InputType;
+import android.text.TextWatcher;
import android.widget.EditText;
import android.widget.TextView;
@@ -22,6 +24,21 @@ public class FormTextFieldWrapper extends FormFieldWrapper {
if ("text-private".equals(field.getType())) {
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
}
+ editText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ editText.setError(null);
+ invokeOnFormFieldValuesEdited();
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ }
+ });
}
@Override
@@ -38,18 +55,43 @@ public class FormTextFieldWrapper extends FormFieldWrapper {
public List<String> getValues() {
List<String> values = new ArrayList<>();
for (String line : getValue().split("\\n")) {
- values.add(line);
+ if (line.length() > 0) {
+ values.add(line);
+ }
}
return values;
}
@Override
+ protected void setValues(List<String> values) {
+ StringBuilder builder = new StringBuilder("");
+ for(int i = 0; i < values.size(); ++i) {
+ builder.append(values.get(i));
+ if (i < values.size() - 1 && "text-multi".equals(field.getType())) {
+ builder.append("\n");
+ }
+ }
+ editText.setText(builder.toString());
+ }
+
+ @Override
public boolean validates() {
- return getValue().trim().length() > 0 || !field.isRequired();
+ if (getValue().trim().length() > 0 || !field.isRequired()) {
+ return true;
+ } else {
+ editText.setError(context.getString(R.string.this_field_is_required));
+ editText.requestFocus();
+ return false;
+ }
}
@Override
protected int getLayoutResource() {
return R.layout.form_text;
}
+
+ @Override
+ void setReadOnly(boolean readOnly) {
+ editText.setEnabled(!readOnly);
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormWrapper.java b/src/main/java/eu/siacs/conversations/ui/forms/FormWrapper.java
new file mode 100644
index 00000000..eafe95cc
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/ui/forms/FormWrapper.java
@@ -0,0 +1,72 @@
+package eu.siacs.conversations.ui.forms;
+
+import android.content.Context;
+import android.widget.LinearLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import eu.siacs.conversations.xmpp.forms.Data;
+import eu.siacs.conversations.xmpp.forms.Field;
+
+public class FormWrapper {
+
+ private final LinearLayout layout;
+
+ private final Data form;
+
+ private final List<FormFieldWrapper> fieldWrappers = new ArrayList<>();
+
+ private FormWrapper(Context context, LinearLayout linearLayout, Data form) {
+ this.form = form;
+ this.layout = linearLayout;
+ this.layout.removeAllViews();
+ for(Field field : form.getFields()) {
+ FormFieldWrapper fieldWrapper = FormFieldFactory.createFromField(context,field);
+ if (fieldWrapper != null) {
+ layout.addView(fieldWrapper.getView());
+ fieldWrappers.add(fieldWrapper);
+ }
+ }
+ }
+
+ public Data submit() {
+ for(FormFieldWrapper fieldWrapper : fieldWrappers) {
+ fieldWrapper.submit();
+ }
+ this.form.submit();
+ return this.form;
+ }
+
+ public boolean validates() {
+ boolean validates = true;
+ for(FormFieldWrapper fieldWrapper : fieldWrappers) {
+ validates &= fieldWrapper.validates();
+ }
+ return validates;
+ }
+
+ public void setOnFormFieldValuesEditedListener(FormFieldWrapper.OnFormFieldValuesEdited listener) {
+ for(FormFieldWrapper fieldWrapper : fieldWrappers) {
+ fieldWrapper.setOnFormFieldValuesEditedListener(listener);
+ }
+ }
+
+ public void setReadOnly(boolean b) {
+ for(FormFieldWrapper fieldWrapper : fieldWrappers) {
+ fieldWrapper.setReadOnly(b);
+ }
+ }
+
+ public boolean edited() {
+ boolean edited = false;
+ for(FormFieldWrapper fieldWrapper : fieldWrappers) {
+ edited |= fieldWrapper.edited();
+ }
+ return edited;
+ }
+
+ public static FormWrapper createInLayout(Context context, LinearLayout layout, Data form) {
+ return new FormWrapper(context, layout, form);
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
index 87790d64..306d50c2 100644
--- a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
@@ -5,6 +5,7 @@ import android.content.Context;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.Network;
+import android.net.RouteInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
@@ -75,15 +76,29 @@ public class DNSHelper {
for(int i = 0; i < networks.length; ++i) {
LinkProperties linkProperties = connectivityManager.getLinkProperties(networks[i]);
if (linkProperties != null) {
- servers.addAll(linkProperties.getDnsServers());
+ if (hasDefaultRoute(linkProperties)) {
+ servers.addAll(0, linkProperties.getDnsServers());
+ } else {
+ servers.addAll(linkProperties.getDnsServers());
+ }
}
}
if (servers.size() > 0) {
- Log.d(Config.LOGTAG,"used lollipop variant to discover dns servers in "+networks.length+" networks");
+ Log.d(Config.LOGTAG, "used lollipop variant to discover dns servers in " + networks.length + " networks");
}
return servers.size() > 0 ? servers : getDnsServersPreLollipop();
}
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private static boolean hasDefaultRoute(LinkProperties linkProperties) {
+ for(RouteInfo route: linkProperties.getRoutes()) {
+ if (route.isDefaultRoute()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private static List<InetAddress> getDnsServersPreLollipop() {
List<InetAddress> servers = new ArrayList<>();
String[] dns = client.findDNS();
diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
index 556688fd..635d2b9b 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
@@ -244,6 +244,7 @@ public class XmppConnection implements Runnable {
tagWriter = new TagWriter();
this.changeStatus(Account.State.CONNECTING);
final boolean useTor = mXmppConnectionService.useTorToConnect() || account.isOnion();
+ final boolean extended = mXmppConnectionService.showExtendedConnectionOptions();
if (useTor) {
String destination;
if (account.getHostname() == null || account.getHostname().isEmpty()) {
@@ -251,8 +252,16 @@ public class XmppConnection implements Runnable {
} else {
destination = account.getHostname();
}
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": connect to "+destination+" via TOR");
- socket = SocksSocketFactory.createSocketOverTor(destination,account.getPort());
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": connect to " + destination + " via TOR");
+ socket = SocksSocketFactory.createSocketOverTor(destination, account.getPort());
+ startXmpp();
+ } else if (extended && account.getHostname() != null && !account.getHostname().isEmpty()) {
+ socket = new Socket();
+ try {
+ socket.connect(new InetSocketAddress(account.getHostname(), account.getPort()), Config.SOCKET_TIMEOUT * 1000);
+ } catch (IOException e) {
+ throw new UnknownHostException();
+ }
startXmpp();
} else if (DNSHelper.isIp(account.getServer().toString())) {
socket = new Socket();
@@ -541,7 +550,7 @@ public class XmppConnection implements Runnable {
} else if (nextTag.isStart("failed")) {
tagReader.readElement(nextTag);
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": resumption failed");
- streamId = null;
+ resetStreamId();
if (account.getStatus() != Account.State.ONLINE) {
sendBindRequest();
}
@@ -1269,7 +1278,6 @@ public class XmppConnection implements Runnable {
}
return;
} else {
- resetStreamId();
if (tagWriter.isActive()) {
tagWriter.finish();
try {
diff --git a/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java b/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java
index d05c9abb..0053a399 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java
@@ -53,16 +53,16 @@ public class Data extends Element {
public void submit() {
this.setAttribute("type","submit");
- removeNonFieldChildren();
+ removeUnnecessaryChildren();
for(Field field : getFields()) {
field.removeNonValueChildren();
}
}
- private void removeNonFieldChildren() {
+ private void removeUnnecessaryChildren() {
for(Iterator<Element> iterator = this.children.iterator(); iterator.hasNext();) {
Element element = iterator.next();
- if (!element.getName().equals("field")) {
+ if (!element.getName().equals("field") && !element.getName().equals("title")) {
iterator.remove();
}
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/forms/Field.java b/src/main/java/eu/siacs/conversations/xmpp/forms/Field.java
index c1fc808d..020b34b9 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/forms/Field.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/forms/Field.java
@@ -1,7 +1,9 @@
package eu.siacs.conversations.xmpp.forms;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
+import java.util.List;
import eu.siacs.conversations.xml.Element;
@@ -52,6 +54,19 @@ public class Field extends Element {
return findChildContent("value");
}
+ public List<String> getValues() {
+ List<String> values = new ArrayList<>();
+ for(Element child : getChildren()) {
+ if ("value".equals(child.getName())) {
+ String content = child.getContent();
+ if (content != null) {
+ values.add(content);
+ }
+ }
+ }
+ return values;
+ }
+
public String getLabel() {
return getAttribute("label");
}
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index a354435d..9c472d0c 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -242,6 +242,7 @@
<string name="you">You</string>
<string name="action_edit_subject">Edit conference subject</string>
<string name="conference_not_found">Conference not found</string>
+ <string name="conference_unknown_error">Unknown error received</string>2
<string name="leave">Leave</string>
<string name="contact_added_you">Contact added you to contact list</string>
<string name="add_back">Add back</string>
@@ -330,6 +331,8 @@
<string name="pref_expert_options_other">Other</string>
<string name="pref_conference_name">Conference name</string>
<string name="pref_conference_name_summary">Use room’s subject instead of JID to identify conferences</string>
+ <string name="pref_autojoin">Automatically join conferences</string>
+ <string name="pref_autojoin_summary">Respect the autojoin flag in conference bookmarks</string>
<string name="toast_message_otr_fingerprint">OTR fingerprint copied to clipboard!</string>
<string name="toast_message_omemo_fingerprint">OMEMO fingerprint copied to clipboard!</string>
<string name="conference_banned">You are banned from this conference</string>
@@ -529,6 +532,9 @@
<string name="pref_away_when_screen_off_summary">Marks your resource as away when the screen is turned off</string>
<string name="pref_xa_on_silent_mode">Not available in silent mode</string>
<string name="pref_xa_on_silent_mode_summary">Marks your resource as not available when device is in silent mode</string>
+ <string name="pref_show_connection_options">Extended connection options</string>
+ <string name="pref_show_connection_options_summary">Show hostname and port options when setting up an account</string>
+ <string name="hostname_example">xmpp.example.com</string>
<string name="action_add_account_with_certificate">Add account with certificate</string>
<string name="unable_to_parse_certificate">Unable to parse certificate</string>
<string name="authenticate_with_certificate">Leave empty to authenticate w/ certificate</string>
@@ -581,4 +587,5 @@
<string name="disable">Disable</string>
<string name="selection_too_large">The selected area is too large</string>
<string name="no_accounts">(No activated accounts)</string>
+ <string name="this_field_is_required">This field is required</string>
</resources>
diff --git a/src/main/res/xml/preferences.xml b/src/main/res/xml/preferences.xml
index b3afe0c3..382b3199 100644
--- a/src/main/res/xml/preferences.xml
+++ b/src/main/res/xml/preferences.xml
@@ -155,6 +155,11 @@
android:key="use_tor"
android:summary="@string/pref_use_tor_summary"
android:title="@string/pref_use_tor"/>
+ <CheckBoxPreference
+ android:defaultValue="false"
+ android:key="show_connection_options"
+ android:summary="@string/pref_show_connection_options_summary"
+ android:title="@string/pref_show_connection_options"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_input_options">
<CheckBoxPreference
@@ -182,6 +187,12 @@
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_expert_options_other">
<CheckBoxPreference
+ android:key="autojoin"
+ android:defaultValue="true"
+ android:title="@string/pref_autojoin"
+ android:summary="@string/pref_autojoin_summary"
+ />
+ <CheckBoxPreference
android:defaultValue="false"
android:key="indicate_received"
android:summary="@string/pref_use_indicate_received_summary"