aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/eu/siacs/conversations/Config.java35
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java154
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Bookmark.java13
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Contact.java11
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Conversation.java90
-rw-r--r--src/main/java/eu/siacs/conversations/entities/ListItem.java12
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Message.java24
-rw-r--r--src/main/java/eu/siacs/conversations/entities/MucOptions.java13
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Roster.java16
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Transferable.java9
-rw-r--r--src/main/java/eu/siacs/conversations/generator/IqGenerator.java7
-rw-r--r--src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java2
-rw-r--r--src/main/java/eu/siacs/conversations/parser/MessageParser.java27
-rw-r--r--src/main/java/eu/siacs/conversations/parser/PresenceParser.java8
-rw-r--r--src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java7
-rw-r--r--src/main/java/eu/siacs/conversations/services/ExportLogsService.java2
-rw-r--r--src/main/java/eu/siacs/conversations/services/XmppConnectionService.java115
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java9
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java10
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConversationActivity.java33
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConversationFragment.java24
-rw-r--r--src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java9
-rw-r--r--src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java19
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ExportLogsPreference.java7
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java5
-rw-r--r--src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java62
-rw-r--r--src/main/java/eu/siacs/conversations/ui/SettingsActivity.java41
-rw-r--r--src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java21
-rw-r--r--src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java190
-rw-r--r--src/main/java/eu/siacs/conversations/ui/XmppActivity.java16
-rw-r--r--src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java4
-rw-r--r--src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java15
-rw-r--r--src/main/java/eu/siacs/conversations/utils/UIHelper.java5
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java8
34 files changed, 728 insertions, 295 deletions
diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java
index 4d64c3aa..e39cd654 100644
--- a/src/main/java/eu/siacs/conversations/Config.java
+++ b/src/main/java/eu/siacs/conversations/Config.java
@@ -6,13 +6,44 @@ import eu.siacs.conversations.xmpp.chatstate.ChatState;
public final class Config {
+
+ private static final int UNENCRYPTED = 1;
+ private static final int OPENPGP = 2;
+ private static final int OTR = 4;
+ private static final int OMEMO = 8;
+
+ private static final int ENCRYPTION_MASK = UNENCRYPTED | OPENPGP | OTR | OMEMO;
+
+ public static boolean supportUnencrypted() {
+ return (ENCRYPTION_MASK & UNENCRYPTED) != 0;
+ }
+
+ public static boolean supportOpenPgp() {
+ return (ENCRYPTION_MASK & OPENPGP) != 0;
+ }
+
+ public static boolean supportOtr() {
+ return (ENCRYPTION_MASK & OTR) != 0;
+ }
+
+ public static boolean supportOmemo() {
+ return (ENCRYPTION_MASK & OMEMO) != 0;
+ }
+
+ public static boolean multipleEncryptionChoices() {
+ return (ENCRYPTION_MASK & (ENCRYPTION_MASK - 1)) != 0;
+ }
+
public static final String LOGTAG = "conversations";
public static final String DOMAIN_LOCK = null; //only allow account creation for this domain
+ public static final String CONFERENCE_DOMAIN_LOCK = null; //only allow conference creation for this domain
+ public static final boolean LOCK_DOMAINS_IN_CONVERSATIONS = false; //only add contacts and conferences for own domains
+
+ public static final boolean LOCK_SETTINGS = false; //set to true to disallow account and settings editing
public static final boolean DISALLOW_REGISTRATION_IN_UI = false; //hide the register checkbox
- public static final boolean HIDE_PGP_IN_UI = false; //some more consumer focused clients might want to disable OpenPGP
- public static final boolean FORCE_E2E_ENCRYPTION = false; //disables ability to send unencrypted 1-on-1
+
public static final boolean ALLOW_NON_TLS_CONNECTIONS = false; //very dangerous. you should have a good reason to set this to true
public static final boolean FORCE_ORBOT = false; // always use TOR
public static final boolean HIDE_MESSAGE_TEXT_IN_NOTIFICATION = false;
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 949975aa..cc5c2491 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
@@ -81,13 +81,14 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
}
- public boolean fetchMapHasErrors(Contact contact) {
- Jid jid = contact.getJid().toBareJid();
- if (deviceIds.get(jid) != null) {
- for (Integer foreignId : this.deviceIds.get(jid)) {
- AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId);
- if (fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) {
- return true;
+ public boolean fetchMapHasErrors(List<Jid> jids) {
+ for(Jid jid : jids) {
+ if (deviceIds.get(jid) != null) {
+ for (Integer foreignId : this.deviceIds.get(jid)) {
+ AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId);
+ if (fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) {
+ return true;
+ }
}
}
}
@@ -242,12 +243,29 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
return axolotlStore.getContactKeysWithTrust(account.getJid().toBareJid().toString(), trust);
}
- public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, Contact contact) {
- return axolotlStore.getContactKeysWithTrust(contact.getJid().toBareJid().toString(), trust);
+ public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, Jid jid) {
+ return axolotlStore.getContactKeysWithTrust(jid.toBareJid().toString(), trust);
+ }
+
+ public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, List<Jid> jids) {
+ Set<IdentityKey> keys = new HashSet<>();
+ for(Jid jid : jids) {
+ keys.addAll(axolotlStore.getContactKeysWithTrust(jid.toString(), trust));
+ }
+ return keys;
}
- public long getNumTrustedKeys(Contact contact) {
- return axolotlStore.getContactNumTrustedKeys(contact.getJid().toBareJid().toString());
+ public long getNumTrustedKeys(Jid jid) {
+ return axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toString());
+ }
+
+ public boolean anyTargetHasNoTrustedKeys(List<Jid> jids) {
+ for(Jid jid : jids) {
+ if (axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toString()) == 0) {
+ return true;
+ }
+ }
+ return false;
}
private AxolotlAddress getAddressForJid(Jid jid) {
@@ -259,11 +277,19 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
return new HashSet<>(this.sessions.getAll(ownAddress).values());
}
- private Set<XmppAxolotlSession> findSessionsforContact(Contact contact) {
+ private Set<XmppAxolotlSession> findSessionsForContact(Contact contact) {
AxolotlAddress contactAddress = getAddressForJid(contact.getJid());
return new HashSet<>(this.sessions.getAll(contactAddress).values());
}
+ private Set<XmppAxolotlSession> findSessionsForConversation(Conversation conversation) {
+ HashSet<XmppAxolotlSession> sessions = new HashSet<>();
+ for(Jid jid : conversation.getAcceptedCryptoTargets()) {
+ sessions.addAll(this.sessions.getAll(getAddressForJid(jid)).values());
+ }
+ return sessions;
+ }
+
public Set<String> getFingerprintsForOwnSessions() {
Set<String> fingerprints = new HashSet<>();
for (XmppAxolotlSession session : findOwnSessions()) {
@@ -274,15 +300,14 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
public Set<String> getFingerprintsForContact(final Contact contact) {
Set<String> fingerprints = new HashSet<>();
- for (XmppAxolotlSession session : findSessionsforContact(contact)) {
+ for (XmppAxolotlSession session : findSessionsForContact(contact)) {
fingerprints.add(session.getFingerprint());
}
return fingerprints;
}
- private boolean hasAny(Contact contact) {
- AxolotlAddress contactAddress = getAddressForJid(contact.getJid());
- return sessions.hasAny(contactAddress);
+ private boolean hasAny(Jid jid) {
+ return sessions.hasAny(getAddressForJid(jid));
}
public boolean isPepBroken() {
@@ -594,10 +619,25 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
});
}
- public boolean isContactAxolotlCapable(Contact contact) {
- Jid jid = contact.getJid().toBareJid();
- return hasAny(contact) ||
- (deviceIds.containsKey(jid) && !deviceIds.get(jid).isEmpty());
+ public boolean isConversationAxolotlCapable(Conversation conversation) {
+ final List<Jid> jids = getCryptoTargets(conversation);
+ for(Jid jid : jids) {
+ if (!hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty())) {
+ return false;
+ }
+ }
+ return jids.size() > 0;
+ }
+
+ public List<Jid> getCryptoTargets(Conversation conversation) {
+ final List<Jid> jids;
+ if (conversation.getMode() == Conversation.MODE_SINGLE) {
+ jids = Arrays.asList(conversation.getJid().toBareJid());
+ } else {
+ jids = conversation.getMucOptions().getMembers();
+ jids.remove(account.getJid().toBareJid());
+ }
+ return jids;
}
public XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) {
@@ -753,33 +793,31 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
public Set<AxolotlAddress> findDevicesWithoutSession(final Conversation conversation) {
- return findDevicesWithoutSession(conversation.getContact().getJid().toBareJid());
- }
-
- public Set<AxolotlAddress> findDevicesWithoutSession(final Jid contactJid) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + contactJid);
Set<AxolotlAddress> addresses = new HashSet<>();
- if (deviceIds.get(contactJid) != null) {
- for (Integer foreignId : this.deviceIds.get(contactJid)) {
- AxolotlAddress address = new AxolotlAddress(contactJid.toString(), foreignId);
- if (sessions.get(address) == null) {
- IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
- if (identityKey != null) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
- XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
- sessions.put(address, session);
- } else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + contactJid + ":" + foreignId);
- if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
- addresses.add(address);
+ for(Jid jid : getCryptoTargets(conversation)) {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + jid);
+ if (deviceIds.get(jid) != null) {
+ for (Integer foreignId : this.deviceIds.get(jid)) {
+ AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId);
+ if (sessions.get(address) == null) {
+ IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
+ if (identityKey != null) {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
+ XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
+ sessions.put(address, session);
} else {
- Log.d(Config.LOGTAG,getLogprefix(account)+"skipping over "+address+" because it's broken");
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + jid + ":" + foreignId);
+ if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
+ addresses.add(address);
+ } else {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken");
+ }
}
}
}
+ } else {
+ Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!");
}
- } else {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!");
}
if (deviceIds.get(account.getJid().toBareJid()) != null) {
for (Integer ownId : this.deviceIds.get(account.getJid().toBareJid())) {
@@ -827,7 +865,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
public boolean trustedSessionVerified(final Conversation conversation) {
- Set<XmppAxolotlSession> sessions = findSessionsforContact(conversation.getContact());
+ Set<XmppAxolotlSession> sessions = findSessionsForConversation(conversation);
sessions.addAll(findOwnSessions());
boolean verified = false;
for(XmppAxolotlSession session : sessions) {
@@ -842,26 +880,32 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
return verified;
}
- public boolean hasPendingKeyFetches(Account account, Contact contact) {
+ public boolean hasPendingKeyFetches(Account account, List<Jid> jids) {
AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0);
- AxolotlAddress foreignAddress = new AxolotlAddress(contact.getJid().toBareJid().toString(), 0);
- return fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)
- || fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING);
-
+ if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)) {
+ return true;
+ }
+ for(Jid jid : jids) {
+ AxolotlAddress foreignAddress = new AxolotlAddress(jid.toBareJid().toString(), 0);
+ if (fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING)) {
+ return true;
+ }
+ }
+ return false;
}
@Nullable
- private XmppAxolotlMessage buildHeader(Contact contact) {
+ private XmppAxolotlMessage buildHeader(Conversation conversation) {
final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(
- contact.getJid().toBareJid(), getOwnDeviceId());
+ account.getJid().toBareJid(), getOwnDeviceId());
- Set<XmppAxolotlSession> contactSessions = findSessionsforContact(contact);
+ Set<XmppAxolotlSession> remoteSessions = findSessionsForConversation(conversation);
Set<XmppAxolotlSession> ownSessions = findOwnSessions();
- if (contactSessions.isEmpty()) {
+ if (remoteSessions.isEmpty()) {
return null;
}
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl foreign keyElements...");
- for (XmppAxolotlSession session : contactSessions) {
+ for (XmppAxolotlSession session : remoteSessions) {
Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString());
axolotlMessage.addDevice(session);
}
@@ -876,7 +920,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
@Nullable
public XmppAxolotlMessage encrypt(Message message) {
- XmppAxolotlMessage axolotlMessage = buildHeader(message.getContact());
+ XmppAxolotlMessage axolotlMessage = buildHeader(message.getConversation());
if (axolotlMessage != null) {
final String content;
@@ -913,11 +957,11 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
});
}
- public void prepareKeyTransportMessage(final Contact contact, final OnMessageCreatedCallback onMessageCreatedCallback) {
+ public void prepareKeyTransportMessage(final Conversation conversation, final OnMessageCreatedCallback onMessageCreatedCallback) {
executor.execute(new Runnable() {
@Override
public void run() {
- XmppAxolotlMessage axolotlMessage = buildHeader(contact);
+ XmppAxolotlMessage axolotlMessage = buildHeader(conversation);
onMessageCreatedCallback.run(axolotlMessage);
}
});
diff --git a/src/main/java/eu/siacs/conversations/entities/Bookmark.java b/src/main/java/eu/siacs/conversations/entities/Bookmark.java
index acb4bf1a..088dfd8a 100644
--- a/src/main/java/eu/siacs/conversations/entities/Bookmark.java
+++ b/src/main/java/eu/siacs/conversations/entities/Bookmark.java
@@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
+import eu.siacs.conversations.Config;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.jid.Jid;
@@ -58,6 +59,18 @@ public class Bookmark extends Element implements ListItem {
}
@Override
+ public String getDisplayJid() {
+ Jid jid = getJid();
+ if (Config.LOCK_DOMAINS_IN_CONVERSATIONS && jid != null && jid.getDomainpart().equals(Config.CONFERENCE_DOMAIN_LOCK)) {
+ return jid.getLocalpart();
+ } else if (jid != null) {
+ return jid.toString();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
public Jid getJid() {
return this.getAttributeAsJid("jid");
}
diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java
index 9887d3ef..691fc3e4 100644
--- a/src/main/java/eu/siacs/conversations/entities/Contact.java
+++ b/src/main/java/eu/siacs/conversations/entities/Contact.java
@@ -121,6 +121,17 @@ public class Contact implements ListItem, Blockable {
}
}
+ @Override
+ public String getDisplayJid() {
+ if (Config.LOCK_DOMAINS_IN_CONVERSATIONS && jid != null && jid.getDomainpart().equals(Config.DOMAIN_LOCK)) {
+ return jid.getLocalpart();
+ } else if (jid != null) {
+ return jid.toString();
+ } else {
+ return null;
+ }
+ }
+
public String getProfilePhoto() {
return this.photoUri;
}
diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java
index a179d96b..0252ea74 100644
--- a/src/main/java/eu/siacs/conversations/entities/Conversation.java
+++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java
@@ -9,11 +9,13 @@ import net.java.otr4j.session.SessionID;
import net.java.otr4j.session.SessionImpl;
import net.java.otr4j.session.SessionStatus;
+import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.security.interfaces.DSAPublicKey;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
@@ -48,6 +50,7 @@ public class Conversation extends AbstractEntity implements Blockable {
public static final String ATTRIBUTE_MUC_PASSWORD = "muc_password";
public static final String ATTRIBUTE_MUTED_TILL = "muted_till";
public static final String ATTRIBUTE_ALWAYS_NOTIFY = "always_notify";
+ public static final String ATTRIBUTE_CRYPTO_TARGETS = "crypto_targets";
private String name;
private String contactUuid;
@@ -313,6 +316,18 @@ public class Conversation extends AbstractEntity implements Blockable {
return getLongAttribute("last_clear_history", 0);
}
+ public List<Jid> getAcceptedCryptoTargets() {
+ if (mode == MODE_SINGLE) {
+ return Arrays.asList(getJid().toBareJid());
+ } else {
+ return getJidListAttribute(ATTRIBUTE_CRYPTO_TARGETS);
+ }
+ }
+
+ public void setAcceptedCryptoTargets(List<Jid> acceptedTargets) {
+ setAttribute(ATTRIBUTE_CRYPTO_TARGETS, acceptedTargets);
+ }
+
public void setCorrectingMessage(Message correctingMessage) {
this.correctingMessage = correctingMessage;
}
@@ -659,8 +674,8 @@ public class Conversation extends AbstractEntity implements Blockable {
final AxolotlService axolotlService = getAccount().getAxolotlService();
int next = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, -1);
if (next == -1) {
- if (Config.X509_VERIFICATION && mode == MODE_SINGLE) {
- if (axolotlService != null && axolotlService.isContactAxolotlCapable(getContact())) {
+ if (Config.X509_VERIFICATION) {
+ if (axolotlService != null && axolotlService.isConversationAxolotlCapable(this)) {
return Message.ENCRYPTION_AXOLOTL;
} else {
return Message.ENCRYPTION_NONE;
@@ -673,12 +688,20 @@ public class Conversation extends AbstractEntity implements Blockable {
next = outgoing;
}
}
- if (Config.FORCE_E2E_ENCRYPTION && mode == MODE_SINGLE && next <= 0) {
- if (axolotlService != null && axolotlService.isContactAxolotlCapable(getContact())) {
+
+ if (!Config.supportUnencrypted() && next <= 0) {
+ if (Config.supportOmemo()
+ && (axolotlService != null && axolotlService.isConversationAxolotlCapable(this) || !Config.multipleEncryptionChoices())) {
return Message.ENCRYPTION_AXOLOTL;
- } else {
+ } else if (Config.supportOtr() && mode == MODE_SINGLE) {
return Message.ENCRYPTION_OTR;
+ } else if (Config.supportOpenPgp()
+ && (mode == MODE_SINGLE) || !Config.multipleEncryptionChoices()) {
+ return Message.ENCRYPTION_PGP;
}
+ } else if (next == Message.ENCRYPTION_AXOLOTL
+ && (!Config.supportOmemo() || axolotlService == null || !axolotlService.isConversationAxolotlCapable(this))) {
+ next = Message.ENCRYPTION_NONE;
}
return next;
}
@@ -786,20 +809,59 @@ public class Conversation extends AbstractEntity implements Blockable {
}
public boolean setAttribute(String key, String value) {
- try {
- this.attributes.put(key, value);
- return true;
- } catch (JSONException e) {
- return false;
+ synchronized (this.attributes) {
+ try {
+ this.attributes.put(key, value);
+ return true;
+ } catch (JSONException e) {
+ return false;
+ }
+ }
+ }
+
+ public boolean setAttribute(String key, List<Jid> jids) {
+ JSONArray array = new JSONArray();
+ for(Jid jid : jids) {
+ array.put(jid.toBareJid().toString());
+ }
+ synchronized (this.attributes) {
+ try {
+ this.attributes.put(key, array);
+ return true;
+ } catch (JSONException e) {
+ e.printStackTrace();
+ return false;
+ }
}
}
public String getAttribute(String key) {
- try {
- return this.attributes.getString(key);
- } catch (JSONException e) {
- return null;
+ synchronized (this.attributes) {
+ try {
+ return this.attributes.getString(key);
+ } catch (JSONException e) {
+ return null;
+ }
+ }
+ }
+
+ public List<Jid> getJidListAttribute(String key) {
+ ArrayList<Jid> list = new ArrayList<>();
+ synchronized (this.attributes) {
+ try {
+ JSONArray array = this.attributes.getJSONArray(key);
+ for (int i = 0; i < array.length(); ++i) {
+ try {
+ list.add(Jid.fromString(array.getString(i)));
+ } catch (InvalidJidException e) {
+ //ignored
+ }
+ }
+ } catch (JSONException e) {
+ //ignored
+ }
}
+ return list;
}
public int getIntAttribute(String key, int defaultValue) {
diff --git a/src/main/java/eu/siacs/conversations/entities/ListItem.java b/src/main/java/eu/siacs/conversations/entities/ListItem.java
index efc1c2b9..22aedd4b 100644
--- a/src/main/java/eu/siacs/conversations/entities/ListItem.java
+++ b/src/main/java/eu/siacs/conversations/entities/ListItem.java
@@ -5,13 +5,15 @@ import java.util.List;
import eu.siacs.conversations.xmpp.jid.Jid;
public interface ListItem extends Comparable<ListItem> {
- public String getDisplayName();
+ String getDisplayName();
- public Jid getJid();
+ String getDisplayJid();
- public List<Tag> getTags();
+ Jid getJid();
- public final class Tag {
+ List<Tag> getTags();
+
+ final class Tag {
private final String name;
private final int color;
@@ -29,5 +31,5 @@ public interface ListItem extends Comparable<ListItem> {
}
}
- public boolean match(final String needle);
+ boolean match(final String needle);
}
diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java
index 3dfbd787..be9d3d92 100644
--- a/src/main/java/eu/siacs/conversations/entities/Message.java
+++ b/src/main/java/eu/siacs/conversations/entities/Message.java
@@ -52,6 +52,7 @@ public class Message extends AbstractEntity {
public static final String STATUS = "status";
public static final String TYPE = "type";
public static final String CARBON = "carbon";
+ public static final String OOB = "oob";
public static final String EDITED = "edited";
public static final String REMOTE_MSG_ID = "remoteMsgId";
public static final String SERVER_MSG_ID = "serverMsgId";
@@ -72,6 +73,7 @@ public class Message extends AbstractEntity {
protected int status;
protected int type;
protected boolean carbon = false;
+ protected boolean oob = false;
protected String edited = null;
protected String relativeFilePath;
protected boolean read = true;
@@ -107,7 +109,8 @@ public class Message extends AbstractEntity {
null,
null,
true,
- null);
+ null,
+ false);
this.conversation = conversation;
}
@@ -116,7 +119,7 @@ public class Message extends AbstractEntity {
final int encryption, final int status, final int type, final boolean carbon,
final String remoteMsgId, final String relativeFilePath,
final String serverMsgId, final String fingerprint, final boolean read,
- final String edited) {
+ final String edited, final boolean oob) {
this.uuid = uuid;
this.conversationUuid = conversationUUid;
this.counterpart = counterpart;
@@ -133,6 +136,7 @@ public class Message extends AbstractEntity {
this.axolotlFingerprint = fingerprint;
this.read = read;
this.edited = edited;
+ this.oob = oob;
}
public static Message fromCursor(Cursor cursor) {
@@ -173,7 +177,8 @@ public class Message extends AbstractEntity {
cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID)),
cursor.getString(cursor.getColumnIndex(FINGERPRINT)),
cursor.getInt(cursor.getColumnIndex(READ)) > 0,
- cursor.getString(cursor.getColumnIndex(EDITED)));
+ cursor.getString(cursor.getColumnIndex(EDITED)),
+ cursor.getInt(cursor.getColumnIndex(OOB)) > 0);
}
public static Message createStatusMessage(Conversation conversation, String body) {
@@ -219,6 +224,7 @@ public class Message extends AbstractEntity {
values.put(FINGERPRINT, axolotlFingerprint);
values.put(READ,read ? 1 : 0);
values.put(EDITED, edited);
+ values.put(OOB, oob ? 1 : 0);
return values;
}
@@ -554,6 +560,10 @@ public class Message extends AbstractEntity {
return edited;
}
+ public void setOob(boolean isOob) {
+ this.oob = isOob;
+ }
+
public enum Decision {
MUST,
SHOULD,
@@ -576,7 +586,7 @@ public class Message extends AbstractEntity {
if (dotPosition != -1) {
String extension = filename.substring(dotPosition + 1);
// we want the real file extension, not the crypto one
- if (Arrays.asList(Transferable.VALID_CRYPTO_EXTENSIONS).contains(extension)) {
+ if (Transferable.VALID_CRYPTO_EXTENSIONS.contains(extension)) {
return extractRelevantExtension(filename.substring(0,dotPosition));
} else {
return extension;
@@ -610,6 +620,8 @@ public class Message extends AbstractEntity {
URL url = new URL(body);
if (!url.getProtocol().equalsIgnoreCase("http") && !url.getProtocol().equalsIgnoreCase("https")) {
return Decision.NEVER;
+ } else if (oob) {
+ return Decision.MUST;
}
String extension = extractRelevantExtension(url);
if (extension == null) {
@@ -624,8 +636,8 @@ public class Message extends AbstractEntity {
} else {
return Decision.NEVER;
}
- } else if (Arrays.asList(Transferable.VALID_IMAGE_EXTENSIONS).contains(extension)
- || Arrays.asList(Transferable.WELL_KNOWN_EXTENSIONS).contains(extension)) {
+ } else if (Transferable.VALID_IMAGE_EXTENSIONS.contains(extension)
+ || Transferable.WELL_KNOWN_EXTENSIONS.contains(extension)) {
return Decision.SHOULD;
} else {
return Decision.NEVER;
diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java
index 3567c14f..4fe26743 100644
--- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java
+++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java
@@ -4,9 +4,11 @@ import android.annotation.SuppressLint;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import eu.siacs.conversations.R;
import eu.siacs.conversations.xmpp.forms.Data;
@@ -245,7 +247,8 @@ public class MucOptions {
private Account account;
private final Map<String, User> users = Collections.synchronizedMap(new LinkedHashMap<String, User>());
- private List<String> features = new ArrayList<>();
+ private final Set<Jid> members = Collections.synchronizedSet(new HashSet<Jid>());
+ private final List<String> features = new ArrayList<>();
private Data form = new Data();
private Conversation conversation;
private boolean isOnline = false;
@@ -501,4 +504,12 @@ public class MucOptions {
public Conversation getConversation() {
return this.conversation;
}
+
+ public void putMember(Jid jid) {
+ members.add(jid);
+ }
+
+ public List<Jid> getMembers() {
+ return new ArrayList<>(members);
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/entities/Roster.java b/src/main/java/eu/siacs/conversations/entities/Roster.java
index 628a31d1..d3ad9181 100644
--- a/src/main/java/eu/siacs/conversations/entities/Roster.java
+++ b/src/main/java/eu/siacs/conversations/entities/Roster.java
@@ -9,7 +9,7 @@ import eu.siacs.conversations.xmpp.jid.Jid;
public class Roster {
final Account account;
- final HashMap<String, Contact> contacts = new HashMap<>();
+ final HashMap<Jid, Contact> contacts = new HashMap<>();
private String version = null;
public Roster(Account account) {
@@ -21,7 +21,7 @@ public class Roster {
return null;
}
synchronized (this.contacts) {
- Contact contact = contacts.get(jid.toBareJid().toString());
+ Contact contact = contacts.get(jid.toBareJid());
if (contact != null && contact.showInRoster()) {
return contact;
} else {
@@ -32,15 +32,13 @@ public class Roster {
public Contact getContact(final Jid jid) {
synchronized (this.contacts) {
- final Jid bareJid = jid.toBareJid();
- if (contacts.containsKey(bareJid.toString())) {
- return contacts.get(bareJid.toString());
- } else {
- Contact contact = new Contact(bareJid);
+ if (!contacts.containsKey(jid.toBareJid())) {
+ Contact contact = new Contact(jid.toBareJid());
contact.setAccount(account);
- contacts.put(bareJid.toString(), contact);
+ contacts.put(contact.getJid().toBareJid(), contact);
return contact;
}
+ return contacts.get(jid.toBareJid());
}
}
@@ -80,7 +78,7 @@ public class Roster {
contact.setAccount(account);
contact.setOption(Contact.Options.IN_ROSTER);
synchronized (this.contacts) {
- contacts.put(contact.getJid().toBareJid().toString(), contact);
+ contacts.put(contact.getJid().toBareJid(), contact);
}
}
diff --git a/src/main/java/eu/siacs/conversations/entities/Transferable.java b/src/main/java/eu/siacs/conversations/entities/Transferable.java
index 016c81bd..5a47c451 100644
--- a/src/main/java/eu/siacs/conversations/entities/Transferable.java
+++ b/src/main/java/eu/siacs/conversations/entities/Transferable.java
@@ -1,10 +1,13 @@
package eu.siacs.conversations.entities;
+import java.util.Arrays;
+import java.util.List;
+
public interface Transferable {
- String[] VALID_IMAGE_EXTENSIONS = {"webp", "jpeg", "jpg", "png", "jpe"};
- String[] VALID_CRYPTO_EXTENSIONS = {"pgp", "gpg", "otr"};
- String[] WELL_KNOWN_EXTENSIONS = {"pdf","m4a","mp4"};
+ List<String> VALID_IMAGE_EXTENSIONS = Arrays.asList("webp", "jpeg", "jpg", "png", "jpe");
+ List<String> VALID_CRYPTO_EXTENSIONS = Arrays.asList("pgp", "gpg", "otr");
+ List<String> WELL_KNOWN_EXTENSIONS = Arrays.asList("pdf","m4a","mp4","3gp","aac","amr","mp3");
int STATUS_UNKNOWN = 0x200;
int STATUS_CHECKING = 0x201;
diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
index 4395a546..a3b0f650 100644
--- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
+++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
@@ -335,4 +335,11 @@ public class IqGenerator extends AbstractGenerator {
enable.addChild(data);
return packet;
}
+
+ public IqPacket queryAffiliation(Conversation conversation, String affiliation) {
+ IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
+ packet.setTo(conversation.getJid().toBareJid());
+ packet.query("http://jabber.org/protocol/muc#admin").addChild("item").setAttribute("affiliation",affiliation);
+ return packet;
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java
index 455a0b14..84a44715 100644
--- a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java
+++ b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java
@@ -89,7 +89,7 @@ public class HttpDownloadConnection implements Transferable {
this.message.setEncryption(Message.ENCRYPTION_NONE);
}
String extension;
- if (Arrays.asList(VALID_CRYPTO_EXTENSIONS).contains(lastPart)) {
+ if (VALID_CRYPTO_EXTENSIONS.contains(lastPart)) {
extension = secondToLast;
} else {
extension = lastPart;
diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
index 43edb2c3..9470dab8 100644
--- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
@@ -3,10 +3,10 @@ package eu.siacs.conversations.parser;
import android.util.Log;
import android.util.Pair;
-import eu.siacs.conversations.crypto.PgpDecryptionService;
import net.java.otr4j.session.Session;
import net.java.otr4j.session.SessionStatus;
+import java.net.URL;
import java.util.ArrayList;
import java.util.Set;
import java.util.UUID;
@@ -104,7 +104,7 @@ public class MessageParser extends AbstractParser implements
}
}
- private Message parseAxolotlChat(Element axolotlMessage, Jid from, String id, Conversation conversation, int status) {
+ private Message parseAxolotlChat(Element axolotlMessage, Jid from, Conversation conversation, int status) {
Message finishedMessage = null;
AxolotlService service = conversation.getAccount().getAxolotlService();
XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlMessage, from.toBareJid());
@@ -291,7 +291,9 @@ public class MessageParser extends AbstractParser implements
final String body = packet.getBody();
final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user");
final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted");
- final Element replaceElement = packet.findChild("replace","urn:xmpp:message-correct:0");
+ final Element replaceElement = packet.findChild("replace", "urn:xmpp:message-correct:0");
+ final Element oob = packet.findChild("x", "jabber:x:oob");
+ final boolean isOob = oob!= null && body != null && body.equals(oob.findChildContent("url"));
final String replacementId = replaceElement == null ? null : replaceElement.getAttribute("id");
final Element axolotlEncrypted = packet.findChild(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
int status;
@@ -345,7 +347,7 @@ public class MessageParser extends AbstractParser implements
}
}
Message message;
- if (body != null && body.startsWith("?OTR")) {
+ if (body != null && body.startsWith("?OTR") && Config.supportOtr()) {
if (!isForwarded && !isTypeGroupChat && isProperlyAddressed) {
message = parseOtrChat(body, from, remoteMsgId, conversation);
if (message == null) {
@@ -355,10 +357,20 @@ public class MessageParser extends AbstractParser implements
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": ignoring OTR message from "+from+" isForwarded="+Boolean.toString(isForwarded)+", isProperlyAddressed="+Boolean.valueOf(isProperlyAddressed));
message = new Message(conversation, body, Message.ENCRYPTION_NONE, status);
}
- } else if (pgpEncrypted != null) {
+ } else if (pgpEncrypted != null && Config.supportOpenPgp()) {
message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status);
- } else if (axolotlEncrypted != null) {
- message = parseAxolotlChat(axolotlEncrypted, from, remoteMsgId, conversation, status);
+ } else if (axolotlEncrypted != null && Config.supportOmemo()) {
+ Jid origin;
+ if (conversation.getMode() == Conversation.MODE_MULTI) {
+ origin = conversation.getMucOptions().getTrueCounterpart(counterpart.getResourcepart());
+ if (origin == null) {
+ Log.d(Config.LOGTAG,"axolotl message in non anonymous conference received");
+ return;
+ }
+ } else {
+ origin = from;
+ }
+ message = parseAxolotlChat(axolotlEncrypted, origin, conversation, status);
if (message == null) {
return;
}
@@ -375,6 +387,7 @@ public class MessageParser extends AbstractParser implements
message.setServerMsgId(serverMsgId);
message.setCarbon(isCarbon);
message.setTime(timestamp);
+ message.setOob(isOob);
message.markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
if (conversation.getMode() == Conversation.MODE_MULTI) {
Jid trueCounterpart = conversation.getMucOptions().getTrueCounterpart(counterpart.getResourcepart());
diff --git a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java
index 63d28c97..c2782d23 100644
--- a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java
@@ -64,10 +64,14 @@ public class PresenceParser extends AbstractParser implements
Element item = x.findChild("item");
if (item != null && !from.isBareJid()) {
mucOptions.setError(MucOptions.Error.NONE);
- MucOptions.User user = new MucOptions.User(mucOptions,from);
+ MucOptions.User user = new MucOptions.User(mucOptions, from);
user.setAffiliation(item.getAttribute("affiliation"));
user.setRole(item.getAttribute("role"));
- user.setJid(item.getAttributeAsJid("jid"));
+ Jid real = item.getAttributeAsJid("jid");
+ if (real != null) {
+ user.setJid(real);
+ mucOptions.putMember(real.toBareJid());
+ }
if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE) || packet.getFrom().equals(mucOptions.getConversation().getJid())) {
mucOptions.setOnline();
mucOptions.setSelf(user);
diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
index dcba4f74..b975a001 100644
--- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
+++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
@@ -51,7 +51,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
private static DatabaseBackend instance = null;
private static final String DATABASE_NAME = "history";
- private static final int DATABASE_VERSION = 24;
+ private static final int DATABASE_VERSION = 25;
private static String CREATE_CONTATCS_STATEMENT = "create table "
+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
@@ -163,6 +163,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ Message.CARBON + " INTEGER, "
+ Message.EDITED + " TEXT, "
+ Message.READ + " NUMBER DEFAULT 1, "
+ + Message.OOB + " INTEGER, "
+ Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
+ Message.CONVERSATION + ") REFERENCES "
+ Conversation.TABLENAME + "(" + Conversation.UUID
@@ -375,6 +376,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
if (oldVersion < 24 && newVersion >= 24) {
db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.EDITED + " TEXT");
}
+
+ if (oldVersion < 25 && newVersion >= 25) {
+ db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.OOB + " INTEGER");
+ }
}
public static synchronized DatabaseBackend getInstance(Context context) {
diff --git a/src/main/java/eu/siacs/conversations/services/ExportLogsService.java b/src/main/java/eu/siacs/conversations/services/ExportLogsService.java
index 76983a90..87e65931 100644
--- a/src/main/java/eu/siacs/conversations/services/ExportLogsService.java
+++ b/src/main/java/eu/siacs/conversations/services/ExportLogsService.java
@@ -45,9 +45,9 @@ public class ExportLogsService extends Service {
new Thread(new Runnable() {
@Override
public void run() {
- running.set(false);
export();
stopForeground(true);
+ running.set(false);
stopSelf();
}
}).start();
diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
index ec13d6dc..ecf5504b 100644
--- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
@@ -254,6 +254,14 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
@Override
public void onBind(final Account account) {
+ synchronized (mInProgressAvatarFetches) {
+ for (Iterator<String> iterator = mInProgressAvatarFetches.iterator(); iterator.hasNext(); ) {
+ final String KEY = iterator.next();
+ if (KEY.startsWith(account.getJid().toBareJid() + "_")) {
+ iterator.remove();
+ }
+ }
+ }
account.getRoster().clearPresences();
mJingleConnectionManager.cancelInTransmission();
fetchRosterFromServer(account);
@@ -262,7 +270,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (mPushManagementService.available(account)) {
mPushManagementService.registerPushTokenOnServer(account);
}
- mMessageArchiveService.executePendingQueries(account);
connectMultiModeConversations(account);
syncDirtyContacts(account);
}
@@ -276,6 +283,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
mOnAccountUpdate.onAccountUpdate();
}
if (account.getStatus() == Account.State.ONLINE) {
+ mMessageArchiveService.executePendingQueries(account);
if (connection != null && connection.getFeatures().csi()) {
if (checkListeners()) {
Log.d(Config.LOGTAG, account.getJid().toBareJid() + " sending csi//inactive");
@@ -351,7 +359,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public PgpEngine getPgpEngine() {
- if (pgpServiceConnection != null && pgpServiceConnection.isBound()) {
+ if (!Config.supportOpenPgp()) {
+ return null;
+ } else if (pgpServiceConnection != null && pgpServiceConnection.isBound()) {
if (this.mPgpEngine == null) {
this.mPgpEngine = new PgpEngine(new OpenPgpApi(
getApplicationContext(),
@@ -609,6 +619,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return getPreferences().getBoolean("xa_on_silent_mode", false);
}
+ private boolean treatVibrateAsSilent() {
+ return getPreferences().getBoolean("treat_vibrate_as_silent", false);
+ }
+
private boolean awayWhenScreenOff() {
return getPreferences().getBoolean("away_when_screen_off", false);
}
@@ -643,7 +657,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
private boolean isPhoneSilenced() {
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
- return audioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT;
+ if (treatVibrateAsSilent()) {
+ return audioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
+ } else {
+ return audioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT;
+ }
}
private void resetAllAttemptCounts(boolean reallyAll) {
@@ -689,20 +707,23 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
this.fileObserver.startWatching();
- this.pgpServiceConnection = new OpenPgpServiceConnection(getApplicationContext(), "org.sufficientlysecure.keychain", new OpenPgpServiceConnection.OnBound() {
- @Override
- public void onBound(IOpenPgpService2 service) {
- for (Account account : accounts) {
- if (account.getPgpDecryptionService() != null) {
- account.getPgpDecryptionService().onOpenPgpServiceBound();
+ if (Config.supportOpenPgp()) {
+ this.pgpServiceConnection = new OpenPgpServiceConnection(getApplicationContext(), "org.sufficientlysecure.keychain", new OpenPgpServiceConnection.OnBound() {
+ @Override
+ public void onBound(IOpenPgpService2 service) {
+ for (Account account : accounts) {
+ if (account.getPgpDecryptionService() != null) {
+ account.getPgpDecryptionService().onOpenPgpServiceBound();
+ }
}
}
- }
- @Override
- public void onError(Exception e) { }
- });
- this.pgpServiceConnection.bindToService();
+ @Override
+ public void onError(Exception e) {
+ }
+ });
+ this.pgpServiceConnection.bindToService();
+ }
this.pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "XmppConnectionService");
@@ -1777,8 +1798,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
private void join(Conversation conversation) {
Account account = conversation.getAccount();
- final String nick = conversation.getMucOptions().getProposedNick();
- final Jid joinJid = conversation.getMucOptions().createJoinJid(nick);
+ final MucOptions mucOptions = conversation.getMucOptions();
+ final Jid joinJid = mucOptions.getSelf().getFullJid();
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": joining conversation " + joinJid.toString());
PresencePacket packet = new PresencePacket();
packet.setFrom(conversation.getAccount().getJid());
@@ -1788,7 +1809,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
x.addChild("password").setContent(conversation.getMucOptions().getPassword());
}
- if (conversation.getMucOptions().mamSupport()) {
+ if (mucOptions.mamSupport()) {
// Use MAM instead of the limited muc history to get history
x.addChild("history").setAttribute("maxchars", "0");
} else {
@@ -1807,9 +1828,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
conversation.setContactJid(joinJid);
databaseBackend.updateConversation(conversation);
}
- if (conversation.getMucOptions().mamSupport()) {
+
+ if (mucOptions.mamSupport()) {
getMessageArchiveService().catchupMUC(conversation);
}
+ if (mucOptions.membersOnly() && mucOptions.nonanonymous()) {
+ fetchConferenceMembers(conversation);
+ }
sendUnsentMessages(conversation);
}
@@ -1833,6 +1858,37 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
+ private void fetchConferenceMembers(final Conversation conversation) {
+ final Account account = conversation.getAccount();
+ final String[] affiliations = {"member","admin","owner"};
+ OnIqPacketReceived callback = new OnIqPacketReceived() {
+
+ private int i = 0;
+
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ Element query = packet.query("http://jabber.org/protocol/muc#admin");
+ if (packet.getType() == IqPacket.TYPE.RESULT && query != null) {
+ for(Element child : query.getChildren()) {
+ if ("item".equals(child.getName())) {
+ conversation.getMucOptions().putMember(child.getAttributeAsJid("jid"));
+ }
+ }
+ } else {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not request affiliation "+affiliations[i]+" in "+conversation.getJid().toBareJid());
+ }
+ ++i;
+ if (i >= affiliations.length) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": retrieved members for "+conversation.getJid().toBareJid()+": "+conversation.getMucOptions().getMembers());
+ }
+ }
+ };
+ for(String affiliation : affiliations) {
+ sendIqPacket(account, mIqGenerator.queryAffiliation(conversation, affiliation), callback);
+ }
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": fetching members for "+conversation.getName());
+ }
+
public void providePasswordForMuc(Conversation conversation, String password) {
if (conversation.getMode() == Conversation.MODE_MULTI) {
conversation.getMucOptions().setPassword(password);
@@ -1906,7 +1962,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
account.pendingConferenceLeaves.remove(conversation);
if (account.getStatus() == Account.State.ONLINE || now) {
PresencePacket packet = new PresencePacket();
- packet.setTo(conversation.getJid());
+ packet.setTo(conversation.getMucOptions().getSelf().getFullJid());
packet.setFrom(conversation.getAccount().getJid());
packet.setAttribute("type", "unavailable");
sendPresencePacket(conversation.getAccount(), packet);
@@ -2006,9 +2062,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.RESULT) {
+ Element query = packet.findChild("query","http://jabber.org/protocol/disco#info");
+ if (packet.getType() == IqPacket.TYPE.RESULT && query != null) {
ArrayList<String> features = new ArrayList<>();
- Element query = packet.query();
for (Element child : query.getChildren()) {
if (child != null && child.getName().equals("feature")) {
String var = child.getAttribute("var");
@@ -2025,6 +2081,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (callback != null) {
callback.onConferenceConfigurationFetched(conversation);
}
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": fetched muc configuration for "+conversation.getJid().toBareJid()+" - "+features.toString());
updateConversationUi();
} else if (packet.getType() == IqPacket.TYPE.ERROR) {
if (callback != null) {
@@ -2505,22 +2562,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
private void reconnectAccount(final Account account, final boolean force, final boolean interactive) {
synchronized (account) {
XmppConnection connection = account.getXmppConnection();
- if (connection != null) {
- disconnect(account, force);
- } else {
+ if (connection == null) {
connection = createConnection(account);
account.setXmppConnection(connection);
}
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
- synchronized (this.mInProgressAvatarFetches) {
- for (Iterator<String> iterator = this.mInProgressAvatarFetches.iterator(); iterator.hasNext(); ) {
- final String KEY = iterator.next();
- if (KEY.startsWith(account.getJid().toBareJid() + "_")) {
- iterator.remove();
- }
- }
- }
if (!force) {
+ disconnect(account, false);
try {
Log.d(Config.LOGTAG, "wait for disconnect");
Thread.sleep(500); //sleep wait for disconnect
@@ -2533,6 +2581,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
thread.start();
scheduleWakeUpCall(Config.CONNECT_DISCO_TIMEOUT, account.getUuid().hashCode());
} else {
+ disconnect(account, force);
account.getRoster().clearPresences();
connection.resetEverything();
}
@@ -2624,7 +2673,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public boolean allowMessageCorrection() {
- return getPreferences().getBoolean("allow_message_correction", true);
+ return getPreferences().getBoolean("allow_message_correction", false);
}
public boolean sendChatStates() {
diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
index be454936..adbb0953 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
@@ -6,7 +6,6 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.IntentSender.SendIntentException;
-import android.graphics.Bitmap;
import android.os.Build;
import android.os.Bundle;
import android.view.ContextMenu;
@@ -334,7 +333,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.muc_details, menu);
- return true;
+ return super.onCreateOptionsMenu(menu);
}
@Override
@@ -512,7 +511,11 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
mAccountJid.setText(getString(R.string.using_account, account));
mYourPhoto.setImageBitmap(avatarService().get(mConversation.getAccount(), getPixel(48)));
setTitle(mConversation.getName());
- mFullJid.setText(mConversation.getJid().toBareJid().toString());
+ if (Config.LOCK_DOMAINS_IN_CONVERSATIONS && mConversation.getJid().getDomainpart().equals(Config.CONFERENCE_DOMAIN_LOCK)) {
+ mFullJid.setText(mConversation.getJid().getLocalpart());
+ } else {
+ mFullJid.setText(mConversation.getJid().toBareJid().toString());
+ }
mYourNick.setText(mucOptions.getActualNick());
mRoleAffiliaton = (TextView) findViewById(R.id.muc_role);
if (mucOptions.online()) {
diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
index d50e0661..4165b4be 100644
--- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
@@ -139,7 +139,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
ContactDetailsActivity.this);
builder.setTitle(getString(R.string.action_add_phone_book));
builder.setMessage(getString(R.string.add_phone_book_text,
- contact.getJid()));
+ contact.getDisplayJid()));
builder.setNegativeButton(getString(R.string.cancel), null);
builder.setPositiveButton(getString(R.string.add), addToPhonebook);
builder.create().show();
@@ -236,7 +236,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
builder.setTitle(getString(R.string.action_delete_contact))
.setMessage(
getString(R.string.remove_contact_text,
- contact.getJid()))
+ contact.getDisplayJid()))
.setPositiveButton(getString(R.string.delete),
removeFromRoster).create().show();
break;
@@ -297,7 +297,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
edit.setVisible(false);
delete.setVisible(false);
}
- return true;
+ return super.onCreateOptionsMenu(menu);
}
private void populateView() {
@@ -358,10 +358,10 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
}
if (contact.getPresences().size() > 1) {
- contactJidTv.setText(contact.getJid() + " ("
+ contactJidTv.setText(contact.getDisplayJid() + " ("
+ contact.getPresences().size() + ")");
} else {
- contactJidTv.setText(contact.getJid().toString());
+ contactJidTv.setText(contact.getDisplayJid());
}
String account;
if (Config.DOMAIN_LOCK != null) {
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
index 11e2e889..eac53124 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
@@ -408,9 +408,10 @@ public class ConversationActivity extends XmppActivity
menuContactDetails.setVisible(false);
menuAttach.setVisible(getSelectedConversation().getAccount().httpUploadAvailable() && getSelectedConversation().getMucOptions().participating());
menuInviteContact.setVisible(getSelectedConversation().getMucOptions().canInvite());
- menuSecure.setVisible(!Config.HIDE_PGP_IN_UI && !Config.X509_VERIFICATION); //if pgp is hidden conferences have no choice of encryption
+ menuSecure.setVisible((Config.supportOpenPgp() || Config.supportOmemo()) && Config.multipleEncryptionChoices()); //only if pgp is supported we have a choice
} else {
menuMucDetails.setVisible(false);
+ menuSecure.setVisible(Config.multipleEncryptionChoices());
}
if (this.getSelectedConversation().isMuted()) {
menuMute.setVisible(false);
@@ -419,7 +420,7 @@ public class ConversationActivity extends XmppActivity
}
}
}
- return true;
+ return super.onCreateOptionsMenu(menu);
}
protected void selectPresenceToAttachFile(final int attachmentChoice, final int encryption) {
@@ -849,13 +850,14 @@ public class ConversationActivity extends XmppActivity
MenuItem none = popup.getMenu().findItem(R.id.encryption_choice_none);
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 || conversation.getMode() == Conversation.MODE_MULTI);
- otr.setVisible(!Config.X509_VERIFICATION);
+ pgp.setVisible(Config.supportOpenPgp());
+ none.setVisible(Config.supportUnencrypted() || conversation.getMode() == Conversation.MODE_MULTI);
+ otr.setVisible(Config.supportOtr());
+ axolotl.setVisible(Config.supportOmemo());
if (conversation.getMode() == Conversation.MODE_MULTI) {
otr.setVisible(false);
- axolotl.setVisible(false);
- } else if (!conversation.getAccount().getAxolotlService().isContactAxolotlCapable(conversation.getContact())) {
+ }
+ if (!conversation.getAccount().getAxolotlService().isConversationAxolotlCapable(conversation)) {
axolotl.setEnabled(false);
}
switch (conversation.getNextEncryption()) {
@@ -1528,18 +1530,23 @@ public class ConversationActivity extends XmppActivity
protected boolean trustKeysIfNeeded(int requestCode, int attachmentChoice) {
AxolotlService axolotlService = mSelectedConversation.getAccount().getAxolotlService();
- Contact contact = mSelectedConversation.getContact();
+ final List<Jid> targets = axolotlService.getCryptoTargets(mSelectedConversation);
+ boolean hasUnaccepted = !mSelectedConversation.getAcceptedCryptoTargets().containsAll(targets);
boolean hasUndecidedOwn = !axolotlService.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED).isEmpty();
- boolean hasUndecidedContact = !axolotlService.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED,contact).isEmpty();
+ boolean hasUndecidedContacts = !axolotlService.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED, targets).isEmpty();
boolean hasPendingKeys = !axolotlService.findDevicesWithoutSession(mSelectedConversation).isEmpty();
- boolean hasNoTrustedKeys = axolotlService.getNumTrustedKeys(mSelectedConversation.getContact()) == 0;
- if(hasUndecidedOwn || hasUndecidedContact || hasPendingKeys || hasNoTrustedKeys) {
+ boolean hasNoTrustedKeys = axolotlService.anyTargetHasNoTrustedKeys(targets);
+ if(hasUndecidedOwn || hasUndecidedContacts || hasPendingKeys || hasNoTrustedKeys || hasUnaccepted) {
axolotlService.createSessionsIfNeeded(mSelectedConversation);
Intent intent = new Intent(getApplicationContext(), TrustKeysActivity.class);
- intent.putExtra("contact", mSelectedConversation.getContact().getJid().toBareJid().toString());
+ String[] contacts = new String[targets.size()];
+ for(int i = 0; i < contacts.length; ++i) {
+ contacts[i] = targets.get(i).toString();
+ }
+ intent.putExtra("contacts", contacts);
intent.putExtra(EXTRA_ACCOUNT, mSelectedConversation.getAccount().getJid().toBareJid().toString());
intent.putExtra("choice", attachmentChoice);
- intent.putExtra("has_no_trusted", hasNoTrustedKeys);
+ intent.putExtra("conversation",mSelectedConversation.getUuid());
startActivityForResult(intent, requestCode);
return true;
} else {
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
index 15e4b985..9ef950c4 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
@@ -53,6 +53,7 @@ import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.Presence;
import eu.siacs.conversations.entities.Transferable;
import eu.siacs.conversations.entities.TransferablePlaceholder;
+import eu.siacs.conversations.http.HttpDownloadConnection;
import eu.siacs.conversations.services.MessageArchiveService;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.ui.XmppActivity.OnPresenceSelected;
@@ -123,7 +124,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
private int getIndexOf(String uuid, List<Message> messages) {
if (uuid == null) {
- return 0;
+ return messages.size() - 1;
}
for(int i = 0; i < messages.size(); ++i) {
if (uuid.equals(messages.get(i).getUuid())) {
@@ -164,7 +165,12 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
@Override
public void run() {
final int oldPosition = messagesView.getFirstVisiblePosition();
- Message message = messageList.get(oldPosition);
+ final Message message;
+ if (oldPosition < messageList.size()) {
+ message = messageList.get(oldPosition);
+ } else {
+ message = null;
+ }
String uuid = message != null ? message.getUuid() : null;
View v = messagesView.getChildAt(0);
final int pxOffset = (v == null) ? 0 : v.getTop();
@@ -515,6 +521,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
private void populateContextMenu(ContextMenu menu) {
final Message m = this.selectedMessage;
+ final Transferable t = m.getTransferable();
Message relevantForCorrection = m;
while(relevantForCorrection.mergeable(relevantForCorrection.next())) {
relevantForCorrection = relevantForCorrection.next();
@@ -531,7 +538,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
MenuItem downloadFile = menu.findItem(R.id.download_file);
MenuItem cancelTransmission = menu.findItem(R.id.cancel_transmission);
if ((m.getType() == Message.TYPE_TEXT || m.getType() == Message.TYPE_PRIVATE)
- && m.getTransferable() == null
+ && t == null
&& !GeoHelper.isGeoUri(m.getBody())
&& m.treatAsDownloadable() != Message.Decision.MUST) {
copyText.setVisible(true);
@@ -545,7 +552,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
}
if ((m.getType() != Message.TYPE_TEXT
&& m.getType() != Message.TYPE_PRIVATE
- && m.getTransferable() == null)
+ && t == null)
|| (GeoHelper.isGeoUri(m.getBody()))) {
shareWith.setVisible(true);
}
@@ -554,15 +561,16 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
}
if (m.hasFileOnRemoteHost()
|| GeoHelper.isGeoUri(m.getBody())
- || m.treatAsDownloadable() == Message.Decision.MUST) {
+ || m.treatAsDownloadable() == Message.Decision.MUST
+ || (t != null && t instanceof HttpDownloadConnection)) {
copyUrl.setVisible(true);
}
- if ((m.getType() == Message.TYPE_TEXT && m.getTransferable() == null && m.treatAsDownloadable() != Message.Decision.NEVER)
- || (m.isFileOrImage() && m.getTransferable() instanceof TransferablePlaceholder && m.hasFileOnRemoteHost())){
+ if ((m.getType() == Message.TYPE_TEXT && t == null && m.treatAsDownloadable() != Message.Decision.NEVER)
+ || (m.isFileOrImage() && t instanceof TransferablePlaceholder && m.hasFileOnRemoteHost())){
downloadFile.setVisible(true);
downloadFile.setTitle(activity.getString(R.string.download_x_file,UIHelper.getFileDescriptionString(activity, m)));
}
- if ((m.getTransferable() != null && !(m.getTransferable() instanceof TransferablePlaceholder))
+ if ((t != null && !(t instanceof TransferablePlaceholder))
|| (m.isFileOrImage() && (m.getStatus() == Message.STATUS_WAITING
|| m.getStatus() == Message.STATUS_OFFERED))) {
cancelTransmission.setVisible(true);
diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
index 266ae840..b6f1b430 100644
--- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
@@ -499,7 +499,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
clearDevices.setVisible(false);
mamPrefs.setVisible(false);
}
- return true;
+ return super.onCreateOptionsMenu(menu);
}
@Override
@@ -621,6 +621,13 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
}
+ mPassword.setEnabled(!Config.LOCK_SETTINGS);
+ mAccountJid.setEnabled(!Config.LOCK_SETTINGS);
+ mHostname.setEnabled(!Config.LOCK_SETTINGS);
+ mPort.setEnabled(!Config.LOCK_SETTINGS);
+ mPasswordConfirm.setEnabled(!Config.LOCK_SETTINGS);
+ mRegisterNew.setEnabled(!Config.LOCK_SETTINGS);
+
if (!mInitMode) {
this.mAvatar.setVisibility(View.VISIBLE);
this.mAvatar.setImageBitmap(avatarService().get(this.mAccount, getPixel(72)));
diff --git a/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java b/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java
index bb55420d..a6b3c73c 100644
--- a/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java
+++ b/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java
@@ -2,13 +2,12 @@ package eu.siacs.conversations.ui;
import android.app.AlertDialog;
import android.content.Context;
-import android.content.DialogInterface.OnClickListener;
-import android.content.DialogInterface;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Spinner;
+import android.widget.TextView;
import java.util.List;
@@ -44,12 +43,17 @@ public class EnterJidDialog {
final String title, final String positiveButton,
final String prefilledJid, final String account, boolean allowEditJid
) {
+ final boolean lock = Config.LOCK_DOMAINS_IN_CONVERSATIONS && Config.DOMAIN_LOCK != null;
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(title);
View dialogView = LayoutInflater.from(context).inflate(R.layout.enter_jid_dialog, null);
+ final TextView jabberIdDesc = (TextView) dialogView.findViewById(R.id.jabber_id);
+ jabberIdDesc.setText(lock ? R.string.username : R.string.account_settings_jabber_id);
final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account);
final AutoCompleteTextView jid = (AutoCompleteTextView) dialogView.findViewById(R.id.jid);
- jid.setAdapter(new KnownHostsAdapter(context,android.R.layout.simple_list_item_1, knownHosts));
+ if (!lock) {
+ jid.setAdapter(new KnownHostsAdapter(context, android.R.layout.simple_list_item_1, knownHosts));
+ }
if (prefilledJid != null) {
jid.append(prefilledJid);
if (!allowEditJid) {
@@ -60,6 +64,7 @@ public class EnterJidDialog {
}
}
+ jid.setHint(Config.LOCK_DOMAINS_IN_CONVERSATIONS && Config.DOMAIN_LOCK != null ? R.string.username_hint : R.string.account_settings_example_jabber_id);
if (account == null) {
StartConversationActivity.populateAccountSpinner(context, activatedAccounts, spinner);
@@ -95,9 +100,13 @@ public class EnterJidDialog {
}
final Jid contactJid;
try {
- contactJid = Jid.fromString(jid.getText().toString());
+ if (lock) {
+ contactJid = Jid.fromParts(jid.getText().toString(), Config.DOMAIN_LOCK, null);
+ } else {
+ contactJid = Jid.fromString(jid.getText().toString());
+ }
} catch (final InvalidJidException e) {
- jid.setError(context.getString(R.string.invalid_jid));
+ jid.setError(context.getString(lock ? R.string.invalid_username : R.string.invalid_jid));
return;
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ExportLogsPreference.java b/src/main/java/eu/siacs/conversations/ui/ExportLogsPreference.java
index a4e178bd..bedb4172 100644
--- a/src/main/java/eu/siacs/conversations/ui/ExportLogsPreference.java
+++ b/src/main/java/eu/siacs/conversations/ui/ExportLogsPreference.java
@@ -1,7 +1,10 @@
package eu.siacs.conversations.ui;
+import android.Manifest;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Build;
import android.preference.Preference;
import android.util.AttributeSet;
@@ -22,6 +25,10 @@ public class ExportLogsPreference extends Preference {
}
protected void onClick() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+ && getContext().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
final Intent startIntent = new Intent(getContext(), ExportLogsService.class);
getContext().startService(startIntent);
super.onClick();
diff --git a/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java
index f2511ecb..feac2c62 100644
--- a/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java
@@ -123,7 +123,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
menu.findItem(R.id.mgmt_account_publish_avatar).setVisible(false);
} else {
menu.findItem(R.id.mgmt_account_enable).setVisible(false);
- menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(!Config.HIDE_PGP_IN_UI);
+ menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(Config.supportOpenPgp());
}
menu.setHeaderTitle(this.selectedAccount.getJid().toBareJid().toString());
}
@@ -154,7 +154,10 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
if (Config.X509_VERIFICATION) {
addAccount.setVisible(false);
addAccountWithCertificate.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+ } else {
+ addAccount.setVisible(!Config.LOCK_SETTINGS);
}
+ addAccountWithCertificate.setVisible(!Config.LOCK_SETTINGS);
if (!accountsLeftToEnable()) {
enableAll.setVisible(false);
diff --git a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java
index a457bf96..ee70ee43 100644
--- a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java
@@ -7,6 +7,8 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
@@ -27,13 +29,12 @@ import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.utils.ExifHelper;
import eu.siacs.conversations.utils.FileUtils;
import eu.siacs.conversations.utils.PhoneHelper;
-import eu.siacs.conversations.xmpp.jid.InvalidJidException;
-import eu.siacs.conversations.xmpp.jid.Jid;
import eu.siacs.conversations.xmpp.pep.Avatar;
public class PublishProfilePictureActivity extends XmppActivity {
- private static final int REQUEST_CHOOSE_FILE = 0xac23;
+ private static final int REQUEST_CHOOSE_FILE_AND_CROP = 0xac23;
+ private static final int REQUEST_CHOOSE_FILE = 0xac24;
private ImageView avatar;
private TextView accountTextView;
private TextView hintOrWarning;
@@ -138,7 +139,7 @@ public class PublishProfilePictureActivity extends XmppActivity {
@Override
public void onClick(View v) {
if (hasStoragePermission(REQUEST_CHOOSE_FILE)) {
- chooseAvatar();
+ chooseAvatar(false);
}
}
@@ -146,20 +147,22 @@ public class PublishProfilePictureActivity extends XmppActivity {
this.defaultUri = PhoneHelper.getSefliUri(getApplicationContext());
}
- private void chooseAvatar() {
+ private void chooseAvatar(boolean crop) {
Intent attachFileIntent = new Intent();
attachFileIntent.setType("image/*");
attachFileIntent.setAction(Intent.ACTION_GET_CONTENT);
Intent chooser = Intent.createChooser(attachFileIntent, getString(R.string.attach_file));
- startActivityForResult(chooser, REQUEST_CHOOSE_FILE);
+ startActivityForResult(chooser, crop ? REQUEST_CHOOSE_FILE_AND_CROP : REQUEST_CHOOSE_FILE);
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
if (grantResults.length > 0)
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- if (requestCode == REQUEST_CHOOSE_FILE) {
- chooseAvatar();
+ if (requestCode == REQUEST_CHOOSE_FILE_AND_CROP) {
+ chooseAvatar(true);
+ } else if (requestCode == REQUEST_CHOOSE_FILE) {
+ chooseAvatar(false);
}
} else {
Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show();
@@ -167,11 +170,29 @@ public class PublishProfilePictureActivity extends XmppActivity {
}
@Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.publish_avatar, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ if (item.getItemId() == R.id.action_crop_image) {
+ if (hasStoragePermission(REQUEST_CHOOSE_FILE_AND_CROP)) {
+ chooseAvatar(true);
+ }
+ return true;
+ } else {
+ return onOptionsItemSelected(item);
+ }
+ }
+
+ @Override
protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
switch (requestCode) {
- case REQUEST_CHOOSE_FILE:
+ case REQUEST_CHOOSE_FILE_AND_CROP:
Uri source = data.getData();
String original = FileUtils.getPath(this, source);
if (original != null) {
@@ -181,9 +202,17 @@ public class PublishProfilePictureActivity extends XmppActivity {
final int size = getPixel(192);
Crop.of(source, destination).asSquare().withMaxSize(size, size).start(this);
break;
+ case REQUEST_CHOOSE_FILE:
+ this.avatarUri = data.getData();
+ if (xmppConnectionServiceBound) {
+ loadImageIntoPreview(this.avatarUri);
+ }
+ break;
case Crop.REQUEST_CROP:
this.avatarUri = Uri.fromFile(new File(getCacheDir(), "croppedAvatar"));
- loadImageIntoPreview(this.avatarUri);
+ if (xmppConnectionServiceBound) {
+ loadImageIntoPreview(this.avatarUri);
+ }
break;
}
} else {
@@ -248,21 +277,10 @@ public class PublishProfilePictureActivity extends XmppActivity {
}
}
- private Bitmap loadScaledBitmap(Uri uri, int reqSize) throws FileNotFoundException {
- final BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(getContentResolver().openInputStream(uri), null, options);
- int rotation = ExifHelper.getOrientation(getContentResolver().openInputStream(uri));
- options.inSampleSize = FileBackend.calcSampleSize(options, reqSize);
- options.inJustDecodeBounds = false;
- Bitmap bm = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri), null, options);
- return FileBackend.rotate(bm,rotation);
- }
-
protected void loadImageIntoPreview(Uri uri) {
Bitmap bm = null;
try {
- bm = loadScaledBitmap(uri, getPixel(192));
+ bm = xmppConnectionService.getFileBackend().cropCenterSquare(uri, getPixel(192));
} catch (Exception e) {
e.printStackTrace();
}
diff --git a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
index 07c328b9..750a7421 100644
--- a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
@@ -3,8 +3,10 @@ package eu.siacs.conversations.ui;
import android.app.AlertDialog;
import android.app.FragmentManager;
import android.content.DialogInterface;
+import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.preference.ListPreference;
@@ -19,16 +21,20 @@ import java.security.KeyStoreException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
import java.util.Locale;
import de.duenndns.ssl.MemorizingTrustManager;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.services.ExportLogsService;
import eu.siacs.conversations.xmpp.XmppConnection;
public class SettingsActivity extends XmppActivity implements
OnSharedPreferenceChangeListener {
+
+ public static final int REQUEST_WRITE_LOGS = 0xbf8701;
private SettingsFragment mSettingsFragment;
@Override
@@ -130,6 +136,15 @@ public class SettingsActivity extends XmppActivity implements
return true;
}
});
+
+ final Preference exportLogsPreference = mSettingsFragment.findPreference("export_logs");
+ exportLogsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ hasStoragePermission(REQUEST_WRITE_LOGS);
+ return true;
+ }
+ });
}
@Override
@@ -140,8 +155,13 @@ public class SettingsActivity extends XmppActivity implements
}
@Override
- public void onSharedPreferenceChanged(SharedPreferences preferences,
- String name) {
+ public void onSharedPreferenceChanged(SharedPreferences preferences, String name) {
+ final List<String> resendPresence = Arrays.asList(
+ "confirm_messages",
+ "xa_on_silent_mode",
+ "away_when_screen_off",
+ "allow_message_correction",
+ "treat_vibrate_as_silent");
if (name.equals("resource")) {
String resource = preferences.getString("resource", "mobile")
.toLowerCase(Locale.US);
@@ -160,10 +180,7 @@ public class SettingsActivity extends XmppActivity implements
}
} else if (name.equals("keep_foreground_service")) {
xmppConnectionService.toggleForegroundService();
- } else if (name.equals("confirm_messages")
- || name.equals("xa_on_silent_mode")
- || name.equals("away_when_screen_off")
- || name.equals("allow_message_correction")) {
+ } else if (resendPresence.contains(name)) {
if (xmppConnectionServiceBound) {
if (name.equals("away_when_screen_off")) {
xmppConnectionService.toggleScreenEventReceiver();
@@ -179,6 +196,18 @@ public class SettingsActivity extends XmppActivity implements
}
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ if (grantResults.length > 0)
+ if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ if (requestCode == REQUEST_WRITE_LOGS) {
+ getApplicationContext().startService(new Intent(getApplicationContext(), ExportLogsService.class));
+ }
+ } else {
+ Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show();
+ }
+ }
+
private void displayToast(final String msg) {
runOnUiThread(new Runnable() {
@Override
diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
index 8b3b4607..7d650e5b 100644
--- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
@@ -44,6 +44,7 @@ import android.widget.Checkable;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Spinner;
+import android.widget.TextView;
import android.widget.Toast;
import com.google.zxing.integration.android.IntentIntegrator;
@@ -64,7 +65,6 @@ import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.ListItem;
import eu.siacs.conversations.entities.Presence;
-import eu.siacs.conversations.entities.Presences;
import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
import eu.siacs.conversations.ui.adapter.ListItemAdapter;
@@ -390,7 +390,13 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
final View dialogView = getLayoutInflater().inflate(R.layout.join_conference_dialog, null);
final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account);
final AutoCompleteTextView jid = (AutoCompleteTextView) dialogView.findViewById(R.id.jid);
- jid.setAdapter(new KnownHostsAdapter(this, android.R.layout.simple_list_item_1, mKnownConferenceHosts));
+ final boolean lock = Config.LOCK_DOMAINS_IN_CONVERSATIONS && Config.CONFERENCE_DOMAIN_LOCK != null;
+ final TextView jabberIdDesc = (TextView) dialogView.findViewById(R.id.jabber_id);
+ jabberIdDesc.setText(lock ? R.string.conference_name : R.string.conference_address);
+ jid.setHint(lock ? R.string.conference_name : R.string.conference_address_example);
+ if (!lock) {
+ jid.setAdapter(new KnownHostsAdapter(this, android.R.layout.simple_list_item_1, mKnownConferenceHosts));
+ }
if (prefilledJid != null) {
jid.append(prefilledJid);
}
@@ -416,9 +422,13 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
}
final Jid conferenceJid;
try {
- conferenceJid = Jid.fromString(jid.getText().toString());
+ if (lock) {
+ conferenceJid = Jid.fromParts(jid.getText().toString(),Config.CONFERENCE_DOMAIN_LOCK, null);
+ } else {
+ conferenceJid = Jid.fromString(jid.getText().toString());
+ }
} catch (final InvalidJidException e) {
- jid.setError(getString(R.string.invalid_jid));
+ jid.setError(getString(lock ? R.string.invalid_conference_name : R.string.invalid_jid));
return;
}
@@ -523,7 +533,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
mSearchEditText.append(mInitialJid);
filter(mInitialJid);
}
- return true;
+ return super.onCreateOptionsMenu(menu);
}
@Override
@@ -730,6 +740,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
Presence.Status s = p == null ? Presence.Status.OFFLINE : p.getStatus();
if (contact.showInRoster() && contact.match(needle)
&& (!this.mHideOfflineContacts
+ || (needle != null && !needle.trim().isEmpty())
|| s.compareTo(Presence.Status.OFFLINE) < 0)) {
this.contacts.add(contact);
}
diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java
index eec30798..cc4ba7b2 100644
--- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java
@@ -12,7 +12,9 @@ import android.widget.Toast;
import org.whispersystems.libaxolotl.IdentityKey;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -20,32 +22,29 @@ import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.entities.Contact;
+import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdated {
- private Jid accountJid;
- private Jid contactJid;
+ private List<Jid> contactJids;
- private Contact contact;
private Account mAccount;
+ private Conversation mConversation;
private TextView keyErrorMessage;
private LinearLayout keyErrorMessageCard;
private TextView ownKeysTitle;
private LinearLayout ownKeys;
private LinearLayout ownKeysCard;
- private TextView foreignKeysTitle;
private LinearLayout foreignKeys;
- private LinearLayout foreignKeysCard;
private Button mSaveButton;
private Button mCancelButton;
private AxolotlService.FetchStatus lastFetchReport = AxolotlService.FetchStatus.SUCCESS;
private final Map<String, Boolean> ownKeysToTrust = new HashMap<>();
- private final Map<String, Boolean> foreignKeysToTrust = new HashMap<>();
+ private final Map<Jid,Map<String, Boolean>> foreignKeysToTrust = new HashMap<>();
private final OnClickListener mSaveButtonListener = new OnClickListener() {
@Override
@@ -70,25 +69,16 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
}
@Override
- protected String getShareableUri() {
- if (contact != null) {
- return contact.getShareableUri();
- } else {
- return "";
- }
- }
-
- @Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_trust_keys);
- try {
- this.accountJid = Jid.fromString(getIntent().getExtras().getString(EXTRA_ACCOUNT));
- } catch (final InvalidJidException ignored) {
- }
- try {
- this.contactJid = Jid.fromString(getIntent().getExtras().getString("contact"));
- } catch (final InvalidJidException ignored) {
+ this.contactJids = new ArrayList<>();
+ for(String jid : getIntent().getStringArrayExtra("contacts")) {
+ try {
+ this.contactJids.add(Jid.fromString(jid));
+ } catch (InvalidJidException e) {
+ e.printStackTrace();
+ }
}
keyErrorMessageCard = (LinearLayout) findViewById(R.id.key_error_message_card);
@@ -96,9 +86,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
ownKeysTitle = (TextView) findViewById(R.id.own_keys_title);
ownKeys = (LinearLayout) findViewById(R.id.own_keys_details);
ownKeysCard = (LinearLayout) findViewById(R.id.own_keys_card);
- foreignKeysTitle = (TextView) findViewById(R.id.foreign_keys_title);
- foreignKeys = (LinearLayout) findViewById(R.id.foreign_keys_details);
- foreignKeysCard = (LinearLayout) findViewById(R.id.foreign_keys_card);
+ foreignKeys = (LinearLayout) findViewById(R.id.foreign_keys);
mCancelButton = (Button) findViewById(R.id.cancel_button);
mCancelButton.setOnClickListener(mCancelButtonListener);
mSaveButton = (Button) findViewById(R.id.save_button);
@@ -119,7 +107,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
boolean hasForeignKeys = false;
for(final String fingerprint : ownKeysToTrust.keySet()) {
hasOwnKeys = true;
- addFingerprintRowWithListeners(ownKeys, contact.getAccount(), fingerprint, false,
+ addFingerprintRowWithListeners(ownKeys, mAccount, fingerprint, false,
XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(fingerprint)), false,
new CompoundButton.OnCheckedChangeListener() {
@Override
@@ -132,30 +120,44 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
null
);
}
- for(final String fingerprint : foreignKeysToTrust.keySet()) {
- hasForeignKeys = true;
- addFingerprintRowWithListeners(foreignKeys, contact.getAccount(), fingerprint, false,
- XmppAxolotlSession.Trust.fromBoolean(foreignKeysToTrust.get(fingerprint)), false,
- new CompoundButton.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- foreignKeysToTrust.put(fingerprint, isChecked);
- lockOrUnlockAsNeeded();
- }
- },
- null,
- null
- );
- }
- if(hasOwnKeys) {
- ownKeysTitle.setText(accountJid.toString());
- ownKeysCard.setVisibility(View.VISIBLE);
- }
- if(hasForeignKeys) {
- foreignKeysTitle.setText(contactJid.toString());
- foreignKeysCard.setVisibility(View.VISIBLE);
+ synchronized (this.foreignKeysToTrust) {
+ for (Map.Entry<Jid, Map<String, Boolean>> entry : foreignKeysToTrust.entrySet()) {
+ hasForeignKeys = true;
+ final LinearLayout layout = (LinearLayout) getLayoutInflater().inflate(R.layout.keys_card, foreignKeys, false);
+ final Jid jid = entry.getKey();
+ final TextView header = (TextView) layout.findViewById(R.id.foreign_keys_title);
+ final LinearLayout keysContainer = (LinearLayout) layout.findViewById(R.id.foreign_keys_details);
+ final TextView informNoKeys = (TextView) layout.findViewById(R.id.no_keys_to_accept);
+ header.setText(jid.toString());
+ final Map<String, Boolean> fingerprints = entry.getValue();
+ for (final String fingerprint : fingerprints.keySet()) {
+ addFingerprintRowWithListeners(keysContainer, mAccount, fingerprint, false,
+ XmppAxolotlSession.Trust.fromBoolean(fingerprints.get(fingerprint)), false,
+ new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ fingerprints.put(fingerprint, isChecked);
+ lockOrUnlockAsNeeded();
+ }
+ },
+ null,
+ null
+ );
+ }
+ if (fingerprints.size() == 0) {
+ informNoKeys.setVisibility(View.VISIBLE);
+ informNoKeys.setText(getString(R.string.no_keys_just_confirm,mAccount.getRoster().getContact(jid).getDisplayName()));
+ } else {
+ informNoKeys.setVisibility(View.GONE);
+ }
+ foreignKeys.addView(layout);
+ }
}
+
+ ownKeysTitle.setText(mAccount.getJid().toBareJid().toString());
+ ownKeysCard.setVisibility(hasOwnKeys ? View.VISIBLE : View.GONE);
+ foreignKeys.setVisibility(hasForeignKeys ? View.VISIBLE : View.GONE);
if(hasPendingKeyFetches()) {
setFetching();
lock();
@@ -163,13 +165,15 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
if (!hasForeignKeys && hasNoOtherTrustedKeys()) {
keyErrorMessageCard.setVisibility(View.VISIBLE);
if (lastFetchReport == AxolotlService.FetchStatus.ERROR
- || contact.getAccount().getAxolotlService().fetchMapHasErrors(contact)) {
+ || mAccount.getAxolotlService().fetchMapHasErrors(contactJids)) {
keyErrorMessage.setText(R.string.error_no_keys_to_trust_server_error);
} else {
keyErrorMessage.setText(R.string.error_no_keys_to_trust);
}
- ownKeys.removeAllViews(); ownKeysCard.setVisibility(View.GONE);
- foreignKeys.removeAllViews(); foreignKeysCard.setVisibility(View.GONE);
+ ownKeys.removeAllViews();
+ ownKeysCard.setVisibility(View.GONE);
+ foreignKeys.removeAllViews();
+ foreignKeys.setVisibility(View.GONE);
}
lockOrUnlockAsNeeded();
setDone();
@@ -177,46 +181,58 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
}
private boolean reloadFingerprints() {
+ List<Jid> acceptedTargets = mConversation == null ? new ArrayList<Jid>() : mConversation.getAcceptedCryptoTargets();
ownKeysToTrust.clear();
- foreignKeysToTrust.clear();
AxolotlService service = this.mAccount.getAxolotlService();
Set<IdentityKey> ownKeysSet = service.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED);
- Set<IdentityKey> foreignKeysSet = service.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED, contact);
- if (hasNoOtherTrustedKeys() && ownKeysSet.size() == 0) {
- foreignKeysSet.addAll(service.getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED, contact));
- }
for(final IdentityKey identityKey : ownKeysSet) {
if(!ownKeysToTrust.containsKey(identityKey)) {
ownKeysToTrust.put(identityKey.getFingerprint().replaceAll("\\s", ""), false);
}
}
- for(final IdentityKey identityKey : foreignKeysSet) {
- if(!foreignKeysToTrust.containsKey(identityKey)) {
- foreignKeysToTrust.put(identityKey.getFingerprint().replaceAll("\\s", ""), false);
+ synchronized (this.foreignKeysToTrust) {
+ foreignKeysToTrust.clear();
+ for (Jid jid : contactJids) {
+ Set<IdentityKey> foreignKeysSet = service.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED, jid);
+ if (hasNoOtherTrustedKeys(jid) && ownKeysSet.size() == 0) {
+ foreignKeysSet.addAll(service.getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED, jid));
+ }
+ Map<String, Boolean> foreignFingerprints = new HashMap<>();
+ for (final IdentityKey identityKey : foreignKeysSet) {
+ if (!foreignFingerprints.containsKey(identityKey)) {
+ foreignFingerprints.put(identityKey.getFingerprint().replaceAll("\\s", ""), false);
+ }
+ }
+ if (foreignFingerprints.size() > 0 || !acceptedTargets.contains(jid)) {
+ foreignKeysToTrust.put(jid, foreignFingerprints);
+ }
}
}
- return ownKeysSet.size() + foreignKeysSet.size() > 0;
+ return ownKeysSet.size() + foreignKeysToTrust.size() > 0;
}
@Override
public void onBackendConnected() {
- if ((accountJid != null) && (contactJid != null)) {
- this.mAccount = xmppConnectionService.findAccountByJid(accountJid);
- if (this.mAccount == null) {
- return;
- }
- this.contact = this.mAccount.getRoster().getContact(contactJid);
+ Intent intent = getIntent();
+ this.mAccount = extractAccount(intent);
+ if (this.mAccount != null && intent != null) {
+ String uuid = intent.getStringExtra("conversation");
+ this.mConversation = xmppConnectionService.findConversationByUuid(uuid);
reloadFingerprints();
populateView();
}
}
private boolean hasNoOtherTrustedKeys() {
+ return mAccount == null || mAccount.getAxolotlService().anyTargetHasNoTrustedKeys(contactJids);
+ }
+
+ private boolean hasNoOtherTrustedKeys(Jid contact) {
return mAccount == null || mAccount.getAxolotlService().getNumTrustedKeys(contact) == 0;
}
private boolean hasPendingKeyFetches() {
- return mAccount != null && contact != null && mAccount.getAxolotlService().hasPendingKeyFetches(mAccount,contact);
+ return mAccount != null && mAccount.getAxolotlService().hasPendingKeyFetches(mAccount, contactJids);
}
@@ -262,14 +278,28 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
private void commitTrusts() {
for(final String fingerprint :ownKeysToTrust.keySet()) {
- contact.getAccount().getAxolotlService().setFingerprintTrust(
+ mAccount.getAxolotlService().setFingerprintTrust(
fingerprint,
XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(fingerprint)));
}
- for(final String fingerprint:foreignKeysToTrust.keySet()) {
- contact.getAccount().getAxolotlService().setFingerprintTrust(
- fingerprint,
- XmppAxolotlSession.Trust.fromBoolean(foreignKeysToTrust.get(fingerprint)));
+ List<Jid> acceptedTargets = mConversation == null ? new ArrayList<Jid>() : mConversation.getAcceptedCryptoTargets();
+ synchronized (this.foreignKeysToTrust) {
+ for (Map.Entry<Jid, Map<String, Boolean>> entry : foreignKeysToTrust.entrySet()) {
+ Jid jid = entry.getKey();
+ Map<String, Boolean> value = entry.getValue();
+ if (!acceptedTargets.contains(jid)) {
+ acceptedTargets.add(jid);
+ }
+ for (final String fingerprint : value.keySet()) {
+ mAccount.getAxolotlService().setFingerprintTrust(
+ fingerprint,
+ XmppAxolotlSession.Trust.fromBoolean(value.get(fingerprint)));
+ }
+ }
+ }
+ if (mConversation != null && mConversation.getMode() == Conversation.MODE_MULTI) {
+ mConversation.setAcceptedCryptoTargets(acceptedTargets);
+ xmppConnectionService.updateConversation(mConversation);
}
}
@@ -284,11 +314,17 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
}
private void lockOrUnlockAsNeeded() {
- if (hasNoOtherTrustedKeys() && !foreignKeysToTrust.values().contains(true)){
- lock();
- } else {
- unlock();
+ synchronized (this.foreignKeysToTrust) {
+ for (Jid jid : contactJids) {
+ Map<String, Boolean> fingerprints = foreignKeysToTrust.get(jid);
+ if (hasNoOtherTrustedKeys(jid) && (fingerprints == null || !fingerprints.values().contains(true))) {
+ lock();
+ return;
+ }
+ }
}
+ unlock();
+
}
private void setDone() {
diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
index 003b802c..5682f785 100644
--- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
@@ -42,6 +42,7 @@ import android.preference.PreferenceManager;
import android.text.InputType;
import android.util.DisplayMetrics;
import android.util.Log;
+import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
@@ -377,6 +378,19 @@ public abstract class XmppActivity extends Activity {
}
}
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ final MenuItem menuSettings = menu.findItem(R.id.action_settings);
+ final MenuItem menuManageAccounts = menu.findItem(R.id.action_accounts);
+ if (menuSettings != null) {
+ menuSettings.setVisible(!Config.LOCK_SETTINGS);
+ }
+ if (menuManageAccounts != null) {
+ menuManageAccounts.setVisible(!Config.LOCK_SETTINGS);
+ }
+ return super.onCreateOptionsMenu(menu);
+ }
+
protected boolean showBatteryOptimizationWarning() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
@@ -785,7 +799,7 @@ public abstract class XmppActivity extends Activity {
+ "\n\n" + CryptoHelper.prettifyFingerprint(fingerprint.substring(2))
+ "\n\n" + getString(R.string.purge_key_desc_part2));
builder.setNegativeButton(getString(R.string.cancel), null);
- builder.setPositiveButton(getString(R.string.accept),
+ builder.setPositiveButton(getString(R.string.purge_key),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
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 47414f90..da8e3910 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java
@@ -76,10 +76,10 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
tagLayout.addView(tv);
}
}
- final Jid jid = item.getJid();
+ final String jid = item.getDisplayJid();
if (jid != null) {
tvJid.setVisibility(View.VISIBLE);
- tvJid.setText(jid.toString());
+ tvJid.setText(jid);
} else {
tvJid.setVisibility(View.GONE);
}
diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
index 9339809c..ab9f74a3 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
@@ -32,6 +32,7 @@ import android.widget.TextView;
import android.widget.Toast;
import java.lang.ref.WeakReference;
+import java.net.URL;
import java.util.List;
import java.util.concurrent.RejectedExecutionException;
import java.util.regex.Matcher;
@@ -635,7 +636,19 @@ public class MessageAdapter extends ArrayAdapter<Message> {
} else if (message.bodyIsHeart()) {
displayHeartMessage(viewHolder, message.getBody().trim());
} else if (message.treatAsDownloadable() == Message.Decision.MUST) {
- displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message)));
+ try {
+ URL url = new URL(message.getBody());
+ displayDownloadableMessage(viewHolder,
+ message,
+ activity.getString(R.string.check_x_filesize_on_host,
+ UIHelper.getFileDescriptionString(activity, message),
+ url.getHost()));
+ } catch (Exception e) {
+ displayDownloadableMessage(viewHolder,
+ message,
+ activity.getString(R.string.check_x_filesize,
+ UIHelper.getFileDescriptionString(activity, message)));
+ }
} else {
displayTextMessage(viewHolder, message, darkBackground);
}
diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java
index 5e8af085..8cae8117 100644
--- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java
@@ -187,10 +187,13 @@ public class UIHelper {
UIHelper.getMessageDisplayName(message) + " "), false);
} else if (GeoHelper.isGeoUri(message.getBody())) {
if (message.getStatus() == Message.STATUS_RECEIVED) {
- return new Pair<>(context.getString(R.string.received_location),true);
+ return new Pair<>(context.getString(R.string.received_location), true);
} else {
return new Pair<>(context.getString(R.string.location), true);
}
+ } else if (message.treatAsDownloadable() == Message.Decision.MUST) {
+ return new Pair<>(context.getString(R.string.x_file_offered_for_download,
+ getFileDescriptionString(context,message)),true);
} else{
return new Pair<>(message.getBody().trim(), false);
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java
index 56582f97..dcb13c23 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java
@@ -218,7 +218,7 @@ public class JingleConnection implements Transferable {
public void init(final Message message) {
if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
Conversation conversation = message.getConversation();
- conversation.getAccount().getAxolotlService().prepareKeyTransportMessage(conversation.getContact(), new OnMessageCreatedCallback() {
+ conversation.getAccount().getAxolotlService().prepareKeyTransportMessage(conversation, new OnMessageCreatedCallback() {
@Override
public void run(XmppAxolotlMessage xmppAxolotlMessage) {
if (xmppAxolotlMessage != null) {
@@ -322,14 +322,14 @@ public class JingleConnection implements Transferable {
String[] filename = fileNameElement.getContent()
.toLowerCase(Locale.US).toLowerCase().split("\\.");
String extension = filename[filename.length - 1];
- if (Arrays.asList(VALID_IMAGE_EXTENSIONS).contains(extension)) {
+ if (VALID_IMAGE_EXTENSIONS.contains(extension)) {
message.setType(Message.TYPE_IMAGE);
message.setRelativeFilePath(message.getUuid()+"."+extension);
- } else if (Arrays.asList(VALID_CRYPTO_EXTENSIONS).contains(
+ } else if (VALID_CRYPTO_EXTENSIONS.contains(
filename[filename.length - 1])) {
if (filename.length == 3) {
extension = filename[filename.length - 2];
- if (Arrays.asList(VALID_IMAGE_EXTENSIONS).contains(extension)) {
+ if (VALID_IMAGE_EXTENSIONS.contains(extension)) {
message.setType(Message.TYPE_IMAGE);
message.setRelativeFilePath(message.getUuid()+"."+extension);
} else {