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 d2de25629..9e983a595 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -840,40 +840,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { }); } - public enum AxolotlCapability { - FULL, - MISSING_PRESENCE, - MISSING_KEYS, - WRONG_CONFIGURATION, - NO_MEMBERS - } - - public boolean isConversationAxolotlCapable(Conversation conversation) { - return conversation.isSingleOrPrivateAndNonAnonymous(); - } - - public Pair isConversationAxolotlCapableDetailed(Conversation conversation) { - if (conversation.isSingleOrPrivateAndNonAnonymous()) { - final List jids = getCryptoTargets(conversation); - for (Jid jid : jids) { - if (!hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty())) { - if (conversation.getAccount().getRoster().getContact(jid).mutualPresenceSubscription()) { - return new Pair<>(AxolotlCapability.MISSING_KEYS, jid); - } else { - return new Pair<>(AxolotlCapability.MISSING_PRESENCE, jid); - } - } - } - if (jids.size() > 0) { - return new Pair<>(AxolotlCapability.FULL, null); - } else { - return new Pair<>(AxolotlCapability.NO_MEMBERS, null); - } - } else { - return new Pair<>(AxolotlCapability.WRONG_CONFIGURATION, null); - } - } - public List getCryptoTargets(Conversation conversation) { final List jids; if (conversation.getMode() == Conversation.MODE_SINGLE) { diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 3b56b7752..9a33942f9 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -60,9 +60,10 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl private static final String ATTRIBUTE_NEXT_MESSAGE_TIMESTAMP = "next_message_timestamp"; private static final String ATTRIBUTE_CRYPTO_TARGETS = "crypto_targets"; private static final String ATTRIBUTE_NEXT_ENCRYPTION = "next_encryption"; - public static final String ATTRIBUTE_MEMBERS_ONLY = "members_only"; - public static final String ATTRIBUTE_MODERATED = "moderated"; - public static final String ATTRIBUTE_NON_ANONYMOUS = "non_anonymous"; + static final String ATTRIBUTE_MEMBERS_ONLY = "members_only"; + static final String ATTRIBUTE_MODERATED = "moderated"; + static final String ATTRIBUTE_NON_ANONYMOUS = "non_anonymous"; + public static final String ATTRIBUTE_FORMERLY_PRIVATE_NON_ANONYMOUS = "formerly_private_non_anonymous"; protected final ArrayList messages = new ArrayList<>(); public AtomicBoolean messagesLoaded = new AtomicBoolean(true); protected Account account = null; @@ -74,7 +75,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl private int status; private long created; private int mode; - private JSONObject attributes = new JSONObject(); + private JSONObject attributes; private Jid nextCounterpart; private transient MucOptions mucOptions = null; private boolean messagesLeftOnServer = true; @@ -653,12 +654,11 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl if (Config.OMEMO_EXCEPTIONS.CONTACT_DOMAINS.contains(contact) || Config.OMEMO_EXCEPTIONS.ACCOUNT_DOMAINS.contains(account)) { return false; } - final AxolotlService axolotlService = conversation.getAccount().getAxolotlService(); - return axolotlService != null && axolotlService.isConversationAxolotlCapable(conversation); + return conversation.isSingleOrPrivateAndNonAnonymous() || conversation.getBooleanAttribute(ATTRIBUTE_FORMERLY_PRIVATE_NON_ANONYMOUS, false); } - public void setNextEncryption(int encryption) { - this.setAttribute(ATTRIBUTE_NEXT_ENCRYPTION, String.valueOf(encryption)); + public boolean setNextEncryption(int encryption) { + return this.setAttribute(ATTRIBUTE_NEXT_ENCRYPTION, encryption); } public String getNextMessage() { @@ -772,15 +772,17 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl } public boolean setAttribute(String key, boolean value) { - boolean prev = getBooleanAttribute(key,false); - setAttribute(key,Boolean.toString(value)); - return prev != value; + return setAttribute(key, String.valueOf(value)); } private boolean setAttribute(String key, long value) { return setAttribute(key, Long.toString(value)); } + private boolean setAttribute(String key, int value) { + return setAttribute(key, String.valueOf(value)); + } + public boolean setAttribute(String key, String value) { synchronized (this.attributes) { try { diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index 266b67a6b..8f96a21ab 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -511,7 +511,7 @@ public class MucOptions { return users; } - public String createNameFromParticipants() { + String createNameFromParticipants() { List users = getUsersRelevantForNameAndAvatar(); if (users.size() >= 2) { StringBuilder builder = new StringBuilder(); diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 1262969e6..1622c3899 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -1310,6 +1310,12 @@ public class XmppConnectionService extends Service { boolean saveInDb = addToConversation; message.setStatus(Message.STATUS_WAITING); + if (message.getEncryption() != Message.ENCRYPTION_NONE && conversation.getMode() == Conversation.MODE_MULTI && conversation.isPrivateAndNonAnonymous()) { + if (conversation.setAttribute(Conversation.ATTRIBUTE_FORMERLY_PRIVATE_NON_ANONYMOUS, true)) { + databaseBackend.updateConversation(conversation); + } + } + if (account.isOnlineAndConnected()) { switch (message.getEncryption()) { case Message.ENCRYPTION_NONE: diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index df1c9eeee..a0b1cb59d 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -356,6 +356,8 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.muc_details, menu); + final MenuItem share = menu.findItem(R.id.action_share); + share.setVisible(mConversation != null && !mConversation.isPrivateAndNonAnonymous()); AccountUtils.showHideMenuItems(menu); return super.onCreateOptionsMenu(menu); } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index d5bc6a2a6..0b88cdc4c 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -1254,34 +1254,39 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke if (conversation == null) { return; } + final boolean updated; switch (item.getItemId()) { case R.id.encryption_choice_none: - conversation.setNextEncryption(Message.ENCRYPTION_NONE); + updated = conversation.setNextEncryption(Message.ENCRYPTION_NONE); item.setChecked(true); break; case R.id.encryption_choice_pgp: if (activity.hasPgp()) { if (conversation.getAccount().getPgpSignature() != null) { - conversation.setNextEncryption(Message.ENCRYPTION_PGP); + updated = conversation.setNextEncryption(Message.ENCRYPTION_PGP); item.setChecked(true); } else { + updated = false; activity.announcePgp(conversation.getAccount(), conversation, null, activity.onOpenPGPKeyPublished); } } else { activity.showInstallPgpDialog(); + updated = false; } break; case R.id.encryption_choice_axolotl: Log.d(Config.LOGTAG, AxolotlService.getLogprefix(conversation.getAccount()) + "Enabled axolotl for Contact " + conversation.getContact().getJid()); - conversation.setNextEncryption(Message.ENCRYPTION_AXOLOTL); + updated = conversation.setNextEncryption(Message.ENCRYPTION_AXOLOTL); item.setChecked(true); break; default: - conversation.setNextEncryption(Message.ENCRYPTION_NONE); + updated = conversation.setNextEncryption(Message.ENCRYPTION_NONE); break; } - activity.xmppConnectionService.updateConversation(conversation); + if (updated) { + activity.xmppConnectionService.updateConversation(conversation); + } updateChatMsgHint(); getActivity().invalidateOptionsMenu(); activity.refreshUi(); diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index f0aebb573..946037bd5 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -479,7 +479,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne EnterJidDialog dialog = EnterJidDialog.newInstance( mActivatedAccounts, getString(R.string.add_contact), - getString(R.string.create), + getString(R.string.add), prefilledJid, null, invite == null || !invite.hasFingerprints() diff --git a/src/main/java/eu/siacs/conversations/ui/util/ConversationMenuConfigurator.java b/src/main/java/eu/siacs/conversations/ui/util/ConversationMenuConfigurator.java index 69bb38278..98986e03a 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/ConversationMenuConfigurator.java +++ b/src/main/java/eu/siacs/conversations/ui/util/ConversationMenuConfigurator.java @@ -83,11 +83,17 @@ public class ConversationMenuConfigurator { final MenuItem pgp = menu.findItem(R.id.encryption_choice_pgp); final MenuItem axolotl = menu.findItem(R.id.encryption_choice_axolotl); + final int next = conversation.getNextEncryption(); + boolean visible; if (OmemoSetting.isAlways()) { visible = false; } else if (conversation.getMode() == Conversation.MODE_MULTI) { - visible = (Config.supportOpenPgp() || Config.supportOmemo()) && Config.multipleEncryptionChoices(); + if (next == Message.ENCRYPTION_NONE && !conversation.isPrivateAndNonAnonymous() && !conversation.getBooleanAttribute(Conversation.ATTRIBUTE_FORMERLY_PRIVATE_NON_ANONYMOUS, false)) { + visible = false; + } else { + visible = (Config.supportOpenPgp() || Config.supportOmemo()) && Config.multipleEncryptionChoices(); + } } else { visible = Config.multipleEncryptionChoices(); } @@ -105,10 +111,6 @@ public class ConversationMenuConfigurator { pgp.setVisible(Config.supportOpenPgp()); none.setVisible(Config.supportUnencrypted() || conversation.getMode() == Conversation.MODE_MULTI); axolotl.setVisible(Config.supportOmemo()); - final AxolotlService axolotlService = conversation.getAccount().getAxolotlService(); - if (axolotlService == null || !axolotlService.isConversationAxolotlCapable(conversation)) { - axolotl.setEnabled(false); - } switch (conversation.getNextEncryption()) { case Message.ENCRYPTION_NONE: none.setChecked(true); diff --git a/src/main/java/eu/siacs/conversations/ui/util/MucDetailsContextMenuHelper.java b/src/main/java/eu/siacs/conversations/ui/util/MucDetailsContextMenuHelper.java index 02696b533..1eaf96c69 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/MucDetailsContextMenuHelper.java +++ b/src/main/java/eu/siacs/conversations/ui/util/MucDetailsContextMenuHelper.java @@ -63,7 +63,10 @@ public final class MucDetailsContextMenuHelper { MenuItem removeOwnerPrivileges = menu.findItem(R.id.revoke_owner_privileges); MenuItem removeAdminPrivileges = menu.findItem(R.id.remove_admin_privileges); MenuItem removeFromRoom = menu.findItem(R.id.remove_from_room); + MenuItem managePermisisons = menu.findItem(R.id.manage_permissions); + removeFromRoom.setTitle(isGroupChat ? R.string.remove_from_room : R.string.remove_from_channel); MenuItem banFromConference = menu.findItem(R.id.ban_from_conference); + banFromConference.setTitle(isGroupChat ? R.string.ban_from_conference : R.string.ban_from_channel); MenuItem invite = menu.findItem(R.id.invite); startConversation.setVisible(true); final Contact contact = user.getContact(); @@ -74,8 +77,10 @@ public final class MucDetailsContextMenuHelper { if ((activity instanceof ConferenceDetailsActivity || activity instanceof MucUsersActivity) && user.getRole() == MucOptions.Role.NONE) { invite.setVisible(true); } - if (self.getAffiliation().ranks(MucOptions.Affiliation.ADMIN) && self.getAffiliation().outranks(user.getAffiliation())) { + boolean managePermissionsVisible = false; + if ((self.getAffiliation().ranks(MucOptions.Affiliation.ADMIN) && self.getAffiliation().outranks(user.getAffiliation())) || self.getAffiliation() == MucOptions.Affiliation.OWNER) { if (advancedMode) { + managePermissionsVisible = true; if (!user.getAffiliation().ranks(MucOptions.Affiliation.MEMBER)) { giveMembership.setVisible(true); } else if (user.getAffiliation() == MucOptions.Affiliation.MEMBER) { @@ -86,12 +91,14 @@ public final class MucDetailsContextMenuHelper { } } else { if (!Config.DISABLE_BAN || conversation.getMucOptions().membersOnly()) { + managePermissionsVisible = true; removeFromRoom.setVisible(true); } } } if (self.getAffiliation().ranks(MucOptions.Affiliation.OWNER)) { if (isGroupChat || advancedMode || user.getAffiliation() == MucOptions.Affiliation.OWNER) { + managePermissionsVisible = true; if (!user.getAffiliation().ranks(MucOptions.Affiliation.OWNER)) { giveOwnerPrivileges.setVisible(true); } else if (user.getAffiliation() == MucOptions.Affiliation.OWNER){ @@ -99,6 +106,7 @@ public final class MucDetailsContextMenuHelper { } } if (!isGroupChat || advancedMode || user.getAffiliation() == MucOptions.Affiliation.ADMIN) { + managePermissionsVisible = true; if (!user.getAffiliation().ranks(MucOptions.Affiliation.ADMIN)) { giveAdminPrivileges.setVisible(true); } else if (user.getAffiliation() == MucOptions.Affiliation.ADMIN) { @@ -106,6 +114,7 @@ public final class MucDetailsContextMenuHelper { } } } + managePermisisons.setVisible(managePermissionsVisible); sendPrivateMessage.setVisible(!isGroupChat && mucOptions.allowPm() && user.getRole().ranks(MucOptions.Role.VISITOR)); } else { sendPrivateMessage.setVisible(true); diff --git a/src/main/res/layout/activity_muc_details.xml b/src/main/res/layout/activity_muc_details.xml index b7fb5c76f..54d7081ab 100644 --- a/src/main/res/layout/activity_muc_details.xml +++ b/src/main/res/layout/activity_muc_details.xml @@ -156,7 +156,6 @@ android:layout_alignParentStart="true" android:layout_centerVertical="true" android:layout_toStartOf="@+id/change_conference_button" - android:text="@string/private_conference" android:textAppearance="@style/TextAppearance.Conversations.Body1" android:layout_alignParentLeft="true" android:layout_toLeftOf="@+id/change_conference_button" /> diff --git a/src/main/res/menu/muc_details_context.xml b/src/main/res/menu/muc_details_context.xml index 5095b761b..2b735d1f5 100644 --- a/src/main/res/menu/muc_details_context.xml +++ b/src/main/res/menu/muc_details_context.xml @@ -16,36 +16,41 @@ android:id="@+id/send_private_message" android:title="@string/send_private_message" android:visible="false"/> - - - - - - - - + + + + + + + + + + + + diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 63cacb29f..fb7959971 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -371,13 +371,13 @@ Grant owner privileges Revoke owner privileges Remove from group chat + Remove from channel Could not change affiliation of %s Ban from group chat + Ban from channel You are trying to remove %s from a public group chat. The only way to do that is to ban that user for ever. Ban now Could not change role of %s - Publicly accessible group chat - Private, members only group chat Private group chat configuration Public channel configuration Private, members only @@ -846,6 +846,7 @@ Anyone can invite others. Jabber IDs are visible to admins. Jabber IDs are visible to anyone. - This public channel has no participants. + This public channel has no participants. Invite your contacts or use the share button to distribute its XMPP address. This private group chat has no participants. + Manage permissions