aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md7
-rw-r--r--README.md32
-rw-r--r--build.gradle4
-rw-r--r--proguard-project.txt20
-rw-r--r--proguard-rules.txt27
-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
-rw-r--r--src/main/res/drawable-hdpi/ic_crop_white_24dp.pngbin0 -> 302 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_crop_white_24dp.pngbin0 -> 214 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_crop_white_24dp.pngbin0 -> 272 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_crop_white_24dp.pngbin0 -> 326 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_crop_white_24dp.pngbin0 -> 394 bytes
-rw-r--r--src/main/res/layout/activity_trust_keys.xml29
-rw-r--r--src/main/res/layout/keys_card.xml39
-rw-r--r--src/main/res/menu/publish_avatar.xml8
-rw-r--r--src/main/res/values/strings.xml7
-rw-r--r--src/main/res/xml/preferences.xml8
49 files changed, 826 insertions, 378 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f847e94d..73cb0297 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
###Changelog
+####Version 1.11.0
+* OMEMO encrypted conferences
+
+####Version 1.10.1
+* made message correction opt-in
+* various bug fixes
+
####Version 1.10.0
* Support for XEP-0357: Push Notifications
* Support for XEP-0308: Last Message Correction
diff --git a/README.md b/README.md
index 98433510..12528774 100644
--- a/README.md
+++ b/README.md
@@ -261,7 +261,9 @@ I am available for hire. Contact me via XMPP: `inputmice@siacs.eu`
#### Why are there three end-to-end encryption methods and which one should I choose?
-In most cases OTR should be the encryption method of choice. It works out of the box with most contacts as long as they are online. However, openPGP can, in some cases, (message carbons to multiple clients) be more flexible. Unlike OTR, OMEMO works even when a contact is offline, and works with multiple devices. It also allows asynchronous file-transfer when the server has [HTTP File Upload](http://xmpp.org/extensions/xep-0363.html). However, OMEMO is not as widely supported as OTR and is currently implemented only by Conversations. OMEMO should be preferred over OTR for contacts who use Conversations.
+* OTR is a legacy encryption method. It works out of the box with most contacts as long as they are online.
+* OMEMO works even when a contact is offline, and works with multiple devices. It also allows asynchronous file-transfer when the server has [HTTP File Upload](http://xmpp.org/extensions/xep-0363.html). However, OMEMO is not as widely supported as OTR and is currently implemented only by Conversations and Gajim. OMEMO should be preferred over OTR for contacts who use Conversations.
+* OpenPGP (XEP-0027) is a very old encryption method that has some advantages over OTR but should only be used by experts who know what they are doing.
#### How do I use OpenPGP
@@ -282,12 +284,28 @@ manage accounts and choose renew PGP announcement from the contextual menu.
#### How does the encryption for conferences work?
-For conferences the only supported encryption method is OpenPGP (OTR does not
-work with multiple participants). Every participant has to announce their
-OpenPGP key (see answer above). If you would like to send encrypted messages to
-a conference you have to make sure that you have every participant's public key
-in your OpenKeychain. Right now there is no check in Conversations to ensure
-that. You have to take care of that yourself. Go to the conference details and
+For conferences only OMEMO and OpenPGP are suppored as encryption method. (OTR
+does not work with multiple participants).
+
+##### OMEMO
+
+OMEMO encryption works only in private (members only) conferences that are non-anonymous.
+You need to have presence subscription with every member of the conference.
+You can verify that by going into the conference details, long press every member and start
+a conversation with them. (Or select 'contact details' if they are already in your contact
+list)
+
+The owner of a conference can make a public conference private by going into the conference
+details and hit the settings button (the one with the gears) and select both *private* and
+*members only*.
+
+##### OpenPGP
+
+Every participant has to announce their OpenPGP key (see answer above).
+If you would like to send encrypted messages to a conference you have to make
+sure that you have every participant's public key in your OpenKeychain.
+Right now there is no check in Conversations to ensurethat.
+You have to take care of that yourself. Go to the conference details and
touch every key id (The hexadecimal number below a contact). This will send you
to OpenKeychain which will assist you on adding the key. This works best in
very small conferences with contacts you are already using OpenPGP with. This
diff --git a/build.gradle b/build.gradle
index deaae0e2..1d5bb8cf 100644
--- a/build.gradle
+++ b/build.gradle
@@ -58,8 +58,8 @@ android {
defaultConfig {
minSdkVersion 14
targetSdkVersion 23
- versionCode 127
- versionName "1.10.0"
+ versionCode 130
+ versionName "1.11.0-beta.2"
archivesBaseName += "-$versionName"
applicationId "eu.siacs.conversations"
}
diff --git a/proguard-project.txt b/proguard-project.txt
deleted file mode 100644
index f2fe1559..00000000
--- a/proguard-project.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-# To enable ProGuard in your project, edit project.properties
-# to define the proguard.config property as described in that file.
-#
-# Add project specific ProGuard rules here.
-# By default, the flags in this file are appended to flags specified
-# in ${sdk.dir}/tools/proguard/proguard-android.txt
-# You can edit the include path and order by changing the ProGuard
-# include property in project.properties.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# Add any project specific keep options here:
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
diff --git a/proguard-rules.txt b/proguard-rules.txt
deleted file mode 100644
index f39d07c5..00000000
--- a/proguard-rules.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-# Add project specific ProGuard rules here.
-# By default, the flags in this file are appended to flags specified
-# in /home/sam/android-sdk-linux/tools/proguard/proguard-android.txt
-# You can edit the include path and order by changing the ProGuard
-# include property in project.properties.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# Add any project specific keep options here:
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
-
--dontwarn javax.naming.**
-
--keep class * extends java.util.ListResourceBundle {
- protected Object[][] getContents();
-}
-
--keepnames class * implements android.os.Parcelable {
- public static final ** CREATOR;
-}
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 {
diff --git a/src/main/res/drawable-hdpi/ic_crop_white_24dp.png b/src/main/res/drawable-hdpi/ic_crop_white_24dp.png
new file mode 100644
index 00000000..6cbb87ce
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_crop_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_crop_white_24dp.png b/src/main/res/drawable-mdpi/ic_crop_white_24dp.png
new file mode 100644
index 00000000..d606c072
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_crop_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_crop_white_24dp.png b/src/main/res/drawable-xhdpi/ic_crop_white_24dp.png
new file mode 100644
index 00000000..983af77f
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_crop_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_crop_white_24dp.png b/src/main/res/drawable-xxhdpi/ic_crop_white_24dp.png
new file mode 100644
index 00000000..8c9eecb9
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_crop_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_crop_white_24dp.png b/src/main/res/drawable-xxxhdpi/ic_crop_white_24dp.png
new file mode 100644
index 00000000..35d5434a
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_crop_white_24dp.png
Binary files differ
diff --git a/src/main/res/layout/activity_trust_keys.xml b/src/main/res/layout/activity_trust_keys.xml
index 6f8a1bc9..d7bb7628 100644
--- a/src/main/res/layout/activity_trust_keys.xml
+++ b/src/main/res/layout/activity_trust_keys.xml
@@ -80,34 +80,11 @@
</LinearLayout>
<LinearLayout
- android:id="@+id/foreign_keys_card"
+ android:id="@+id/foreign_keys"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:layout_marginLeft="@dimen/activity_horizontal_margin"
- android:layout_marginRight="@dimen/activity_horizontal_margin"
- android:layout_marginTop="@dimen/activity_vertical_margin"
- android:layout_marginBottom="@dimen/activity_vertical_margin"
- android:background="@drawable/infocard_border"
- android:orientation="vertical"
- android:padding="@dimen/infocard_padding"
- android:visibility="gone">
-
- <TextView
- android:id="@+id/foreign_keys_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textColor="@color/black87"
- android:textSize="?attr/TextSizeHeadline"
- android:textStyle="bold"/>
-
- <LinearLayout
- android:id="@+id/foreign_keys_details"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:divider="?android:dividerHorizontal"
- android:showDividers="middle"
- android:orientation="vertical">
- </LinearLayout>
+ android:visibility="gone"
+ android:orientation="vertical">
</LinearLayout>
diff --git a/src/main/res/layout/keys_card.xml b/src/main/res/layout/keys_card.xml
new file mode 100644
index 00000000..d3271d1b
--- /dev/null
+++ b/src/main/res/layout/keys_card.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout android:id="@+id/foreign_keys_card"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/activity_vertical_margin"
+ android:layout_marginLeft="@dimen/activity_horizontal_margin"
+ android:layout_marginRight="@dimen/activity_horizontal_margin"
+ android:layout_marginTop="@dimen/activity_vertical_margin"
+ android:background="@drawable/infocard_border"
+ android:orientation="vertical"
+ android:padding="@dimen/infocard_padding">
+
+ <TextView
+ android:id="@+id/foreign_keys_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@color/black87"
+ android:textSize="?attr/TextSizeHeadline"
+ android:textStyle="bold"/>
+
+ <LinearLayout
+ android:id="@+id/foreign_keys_details"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:divider="?android:dividerHorizontal"
+ android:orientation="vertical"
+ android:showDividers="middle">
+ </LinearLayout>
+
+ <TextView
+ android:layout_marginTop="8dp"
+ android:id="@+id/no_keys_to_accept"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@color/black87"
+ android:text="@string/no_keys_just_confirm"
+ android:textSize="?attr/TextSizeBody"/>
+</LinearLayout> \ No newline at end of file
diff --git a/src/main/res/menu/publish_avatar.xml b/src/main/res/menu/publish_avatar.xml
new file mode 100644
index 00000000..1bffb296
--- /dev/null
+++ b/src/main/res/menu/publish_avatar.xml
@@ -0,0 +1,8 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item
+ android:id="@+id/action_crop_image"
+ android:showAsAction="always"
+ android:icon="@drawable/ic_crop_white_24dp"
+ android:title="@string/select_image_and_crop"/>
+</menu> \ No newline at end of file
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 8e3629ca..9657ea24 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -344,6 +344,7 @@
<string name="checking_x">Checking %s on HTTP host</string>
<string name="not_connected_try_again">You are not connected. Try again later</string>
<string name="check_x_filesize">Check %s size</string>
+ <string name="check_x_filesize_on_host">Check %1$s size on %2$s</string>
<string name="message_options">Message options</string>
<string name="copy_text">Copy text</string>
<string name="copy_original_url">Copy original URL</string>
@@ -522,6 +523,8 @@
<string name="username">Username</string>
<string name="username_hint">Username</string>
<string name="invalid_username">This is not a valid username</string>
+ <string name="conference_name">Conference name</string>
+ <string name="invalid_conference_name">This is not a valid conference name</string>
<string name="download_failed_server_not_found">Download failed: Server not found</string>
<string name="download_failed_file_not_found">Download failed: File not found</string>
<string name="download_failed_could_not_connect">Download failed: Could not connect to host</string>
@@ -534,6 +537,8 @@
<string name="pref_away_when_screen_off_summary">Marks your resource as away when the screen is turned off</string>
<string name="pref_xa_on_silent_mode">Not available in silent mode</string>
<string name="pref_xa_on_silent_mode_summary">Marks your resource as not available when device is in silent mode</string>
+ <string name="pref_treat_vibrate_as_silent">Treat vibrate as silent mode</string>
+ <string name="pref_treat_vibrate_as_silent_summary">Marks your resource as not available when device is on vibrate</string>
<string name="pref_show_connection_options">Extended connection settings</string>
<string name="pref_show_connection_options_summary">Show hostname and port settings when setting up an account</string>
<string name="hostname_example">xmpp.example.com</string>
@@ -597,4 +602,6 @@
<string name="this_field_is_required">This field is required</string>
<string name="correct_message">Correct message</string>
<string name="send_corrected_message">Send corrected message</string>
+ <string name="no_keys_just_confirm">You already trust this contact. By selecting \'done\' you are just confirming that %s is part of this conference.</string>
+ <string name="select_image_and_crop">Select image and crop</string>
</resources>
diff --git a/src/main/res/xml/preferences.xml b/src/main/res/xml/preferences.xml
index e8747a8b..f505baf7 100644
--- a/src/main/res/xml/preferences.xml
+++ b/src/main/res/xml/preferences.xml
@@ -147,7 +147,7 @@
android:summary="@string/pref_remove_trusted_certificates_summary"
android:title="@string/pref_remove_trusted_certificates_title"/>
<CheckBoxPreference
- android:defaultValue="true"
+ android:defaultValue="false"
android:key="allow_message_correction"
android:title="@string/pref_allow_message_correction"
android:summary="@string/pref_allow_message_correction_summary"/>
@@ -189,6 +189,12 @@
android:key="xa_on_silent_mode"
android:summary="@string/pref_xa_on_silent_mode_summary"
android:title="@string/pref_xa_on_silent_mode"/>
+ <CheckBoxPreference
+ android:dependency="xa_on_silent_mode"
+ android:defaultValue="false"
+ android:key="treat_vibrate_as_silent"
+ android:title="@string/pref_treat_vibrate_as_silent"
+ android:summary="@string/pref_treat_vibrate_as_silent_summary"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_expert_options_other">
<CheckBoxPreference