otr conversations implementation
This commit is contained in:
parent
389074e802
commit
973a48ef62
|
@ -188,6 +188,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
private int mode;
|
||||
private JSONObject attributes;
|
||||
private Jid nextCounterpart;
|
||||
private boolean hasPermanentCounterpart;
|
||||
private transient SessionImpl otrSession;
|
||||
private transient String otrFingerprint = null;
|
||||
private Smp mSmp = new Smp();
|
||||
|
@ -229,6 +230,9 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
this.attributes = new JSONObject();
|
||||
}
|
||||
this.nextCounterpart = nextCounterpart;
|
||||
if (nextCounterpart != null) {
|
||||
hasPermanentCounterpart = true;
|
||||
}
|
||||
}
|
||||
|
||||
public String getContactUuid() {
|
||||
|
@ -937,7 +941,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
values.put(STATUS, status);
|
||||
values.put(MODE, mode);
|
||||
|
||||
if (nextCounterpart != null) {
|
||||
if (nextCounterpart != null && hasPermanentCounterpart) {
|
||||
values.put(NEXT_COUNTERPART, nextCounterpart.toString());
|
||||
}
|
||||
|
||||
|
@ -963,6 +967,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
presence,
|
||||
"xmpp");
|
||||
this.otrSession = new SessionImpl(sessionId, getAccount().getOtrService());
|
||||
android.util.Log.e("41fd", "new session " + System.identityHashCode(this), new RuntimeException());
|
||||
try {
|
||||
if (sendStart) {
|
||||
this.otrSession.startSession();
|
||||
|
@ -981,6 +986,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
}
|
||||
|
||||
public void resetOtrSession() {
|
||||
android.util.Log.e("41fd", "reset " + System.identityHashCode(this), new RuntimeException());
|
||||
this.otrFingerprint = null;
|
||||
this.otrSession = null;
|
||||
this.mSmp.hint = null;
|
||||
|
@ -1109,6 +1115,10 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
return this.nextCounterpart;
|
||||
}
|
||||
|
||||
public boolean hasPermanentCounterpart() {
|
||||
return hasPermanentCounterpart;
|
||||
}
|
||||
|
||||
public void setNextCounterpart(Jid jid) {
|
||||
this.nextCounterpart = jid;
|
||||
}
|
||||
|
@ -1117,6 +1127,11 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
if (!Config.supportOmemo() && !Config.supportOpenPgp() && !Config.supportOtr()) {
|
||||
return Message.ENCRYPTION_NONE;
|
||||
}
|
||||
|
||||
if (Config.supportOtr() && nextCounterpart != null && getMode() == MODE_SINGLE && hasPermanentCounterpart) {
|
||||
return Message.ENCRYPTION_OTR;
|
||||
}
|
||||
|
||||
if (OmemoSetting.isAlways()) {
|
||||
return suitableForOmemoByDefault(this) ? Message.ENCRYPTION_AXOLOTL : Message.ENCRYPTION_NONE;
|
||||
}
|
||||
|
@ -1127,7 +1142,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
defaultEncryption = Message.ENCRYPTION_NONE;
|
||||
}
|
||||
int encryption = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, defaultEncryption);
|
||||
if (encryption == Message.ENCRYPTION_OTR || encryption < 0) {
|
||||
if (encryption < 0) {
|
||||
return defaultEncryption;
|
||||
} else {
|
||||
return encryption;
|
||||
|
@ -1393,14 +1408,14 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
String res2 = nextCounterpart == null ? null : nextCounterpart.getResource();
|
||||
|
||||
if (nextCounterpart == null) {
|
||||
if (!message.isPrivateMessage()) {
|
||||
if (!message.isPrivateMessage() && message.encryption != Message.ENCRYPTION_OTR) {
|
||||
synchronized (this.messages) {
|
||||
this.messages.add(message);
|
||||
actualizeReplyMessages(this.messages, List.of(message));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (message.isPrivateMessage() && Objects.equals(res1, res2)) {
|
||||
if ((message.isPrivateMessage() || message.encryption == Message.ENCRYPTION_OTR) && Objects.equals(res1, res2)) {
|
||||
synchronized (this.messages) {
|
||||
this.messages.add(message);
|
||||
actualizeReplyMessages(this.messages, List.of(message));
|
||||
|
@ -1422,14 +1437,14 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
}
|
||||
|
||||
if (nextCounterpart == null) {
|
||||
if (!message.isPrivateMessage()) {
|
||||
if (!message.isPrivateMessage() && message.encryption != Message.ENCRYPTION_OTR) {
|
||||
synchronized (this.messages) {
|
||||
properListToAdd.add(Math.min(offset, properListToAdd.size()), message);
|
||||
actualizeReplyMessages(properListToAdd, List.of(message));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (message.isPrivateMessage() && Objects.equals(res1, res2)) {
|
||||
if ((message.isPrivateMessage() || message.encryption == Message.ENCRYPTION_OTR) && Objects.equals(res1, res2)) {
|
||||
synchronized (this.messages) {
|
||||
properListToAdd.add(Math.min(offset, properListToAdd.size()), message);
|
||||
actualizeReplyMessages(properListToAdd, List.of(message));
|
||||
|
@ -1453,7 +1468,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
|
||||
if (nextCounterpart == null) {
|
||||
for(Message m : messages) {
|
||||
if (!m.isPrivateMessage()) {
|
||||
if (!m.isPrivateMessage() && m.encryption != Message.ENCRYPTION_OTR) {
|
||||
newM.add(m);
|
||||
}
|
||||
}
|
||||
|
@ -1464,7 +1479,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
String res2 = nextCounterpart == null ? null : nextCounterpart.getResource();
|
||||
|
||||
|
||||
if (m.isPrivateMessage() && Objects.equals(res1, res2)) {
|
||||
if ((m.isPrivateMessage() || m.encryption == Message.ENCRYPTION_OTR) && Objects.equals(res1, res2)) {
|
||||
newM.add(m);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import android.text.Html;
|
|||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
import net.java.otr4j.session.Session;
|
||||
import net.java.otr4j.session.SessionStatus;
|
||||
|
||||
|
@ -594,7 +596,9 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
|||
|
||||
final boolean conversationIsProbablyMuc = isTypeGroupChat || mucUserElement != null || account.getXmppConnection().getMucServersWithholdAccount().contains(counterpart.getDomain().toEscapedString());
|
||||
|
||||
if (conversationIsProbablyMuc && !isTypeGroupChat) {
|
||||
final boolean isOTR = body != null && body.content.startsWith("?OTR") && Config.supportOtr();
|
||||
|
||||
if ((conversationIsProbablyMuc && !isTypeGroupChat) || (!Strings.isNullOrEmpty(counterpart.getResource()) && isOTR)) {
|
||||
nextCounterpart = counterpart;
|
||||
}
|
||||
|
||||
|
@ -658,7 +662,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
|||
}
|
||||
}
|
||||
final Message message;
|
||||
if (body != null && body.content.startsWith("?OTR") && Config.supportOtr()) {
|
||||
if (isOTR) {
|
||||
if (!isForwarded && !isTypeGroupChat && isProperlyAddressed && !conversationMultiMode) {
|
||||
message = parseOtrChat(body.content, from, remoteMsgId, conversation);
|
||||
if (message == null) {
|
||||
|
|
|
@ -50,6 +50,7 @@ import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore;
|
|||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.Conversational;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.entities.PresenceTemplate;
|
||||
import eu.siacs.conversations.entities.Roster;
|
||||
|
@ -887,30 +888,40 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
|||
String comparsionOperation = isForward ? ">?" : "<?";
|
||||
String sorting = isForward ? " ASC" : " DESC";
|
||||
if (timestamp == -1) {
|
||||
if (conversation.getNextCounterpart() == null) {
|
||||
String[] selectionArgs = {conversation.getUuid()};
|
||||
cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
|
||||
+ "=?", selectionArgs, null, null, Message.TIME_SENT
|
||||
+ sorting, String.valueOf(limit));
|
||||
} else {
|
||||
if (conversation.getNextCounterpart() != null && conversation.hasPermanentCounterpart() && conversation.getMode() == Conversational.MODE_MULTI) {
|
||||
String[] selectionArgs = {conversation.getUuid(), String.valueOf(Message.TYPE_PRIVATE), String.valueOf(Message.TYPE_PRIVATE_FILE), conversation.getNextCounterpart().toString()};
|
||||
cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
|
||||
+ "=? and (" + Message.TYPE + "=? or " + Message.TYPE + "=?) and " + Message.COUNTERPART + "=?" , selectionArgs, null, null, Message.TIME_SENT
|
||||
+ sorting, String.valueOf(limit));
|
||||
} else if (conversation.getNextCounterpart() != null && conversation.hasPermanentCounterpart()) {
|
||||
String[] selectionArgs = {conversation.getUuid(), String.valueOf(Message.ENCRYPTION_OTR), conversation.getNextCounterpart().toString()};
|
||||
cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
|
||||
+ "=? and " + Message.ENCRYPTION + "=? and " + Message.COUNTERPART + "=?" , selectionArgs, null, null, Message.TIME_SENT
|
||||
+ sorting, String.valueOf(limit));
|
||||
} else {
|
||||
String[] selectionArgs = {conversation.getUuid()};
|
||||
cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
|
||||
+ "=?", selectionArgs, null, null, Message.TIME_SENT
|
||||
+ sorting, String.valueOf(limit));
|
||||
}
|
||||
} else {
|
||||
if (conversation.getNextCounterpart() == null) {
|
||||
if (conversation.getNextCounterpart() != null && conversation.hasPermanentCounterpart() && conversation.getMode() == Conversational.MODE_MULTI) {
|
||||
String[] selectionArgs = {conversation.getUuid(), String.valueOf(Message.TYPE_PRIVATE), String.valueOf(Message.TYPE_PRIVATE_FILE), conversation.getNextCounterpart().toString(), Long.toString(timestamp)};
|
||||
cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
|
||||
+ "=? and (" + Message.TYPE + "=? or " + Message.TYPE + "=?) and " + Message.COUNTERPART + "=? and " + Message.TIME_SENT + comparsionOperation, selectionArgs, null, null, Message.TIME_SENT
|
||||
+ sorting, String.valueOf(limit));
|
||||
} else if (conversation.getNextCounterpart() != null && conversation.hasPermanentCounterpart()) {
|
||||
String[] selectionArgs = {conversation.getUuid(), String.valueOf(Message.ENCRYPTION_OTR), conversation.getNextCounterpart().toString(), Long.toString(timestamp)};
|
||||
cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
|
||||
+ "=? and " + Message.ENCRYPTION + "=? and " + Message.COUNTERPART + "=? and " + Message.TIME_SENT + comparsionOperation, selectionArgs, null, null, Message.TIME_SENT
|
||||
+ sorting, String.valueOf(limit));
|
||||
} else {
|
||||
String[] selectionArgs = {conversation.getUuid(),
|
||||
Long.toString(timestamp)};
|
||||
cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
|
||||
+ "=? and " + Message.TIME_SENT + comparsionOperation, selectionArgs,
|
||||
null, null, Message.TIME_SENT + sorting,
|
||||
String.valueOf(limit));
|
||||
} else {
|
||||
String[] selectionArgs = {conversation.getUuid(), String.valueOf(Message.TYPE_PRIVATE), String.valueOf(Message.TYPE_PRIVATE_FILE), conversation.getNextCounterpart().toString(), Long.toString(timestamp)};
|
||||
cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
|
||||
+ "=? and (" + Message.TYPE + "=? or " + Message.TYPE + "=?) and " + Message.COUNTERPART + "=? and " + Message.TIME_SENT + comparsionOperation, selectionArgs, null, null, Message.TIME_SENT
|
||||
+ sorting, String.valueOf(limit));
|
||||
}
|
||||
}
|
||||
CursorUtils.upgradeCursorWindowSize(cursor);
|
||||
|
|
|
@ -2550,6 +2550,7 @@ public class XmppConnectionService extends Service {
|
|||
if ((account == null || conversation.getAccount() == account)
|
||||
&& (conversation.getJid().asBareJid().equals(jid.asBareJid()))
|
||||
&& Objects.equal(conversation.getNextCounterpart(), counterpart)
|
||||
&& conversation.hasPermanentCounterpart()
|
||||
) {
|
||||
return conversation;
|
||||
}
|
||||
|
@ -2558,7 +2559,7 @@ public class XmppConnectionService extends Service {
|
|||
for (final Conversation conversation : haystack) {
|
||||
if ((account == null || conversation.getAccount() == account)
|
||||
&& (conversation.getJid().asBareJid().equals(jid.asBareJid()))
|
||||
&& conversation.getNextCounterpart() == null
|
||||
&& (conversation.getNextCounterpart() == null || !conversation.hasPermanentCounterpart())
|
||||
) {
|
||||
return conversation;
|
||||
}
|
||||
|
@ -2616,10 +2617,15 @@ public class XmppConnectionService extends Service {
|
|||
|
||||
public Conversation findOrCreateConversation(final Account account, final Jid jid, final MessageArchiveService.Query query, final boolean muc, final boolean joinAfterCreate, final boolean async, Jid counterpart) {
|
||||
synchronized (this.conversations) {
|
||||
android.util.Log.e("41fd", jid.toString() + " " + counterpart);
|
||||
|
||||
Conversation conversation = find(account, jid, counterpart);
|
||||
if (conversation != null) {
|
||||
return conversation;
|
||||
}
|
||||
|
||||
android.util.Log.e("41fd 1 not find", jid.toString() + " " + counterpart);
|
||||
|
||||
conversation = databaseBackend.findConversation(account, jid, counterpart);
|
||||
final boolean loadMessagesFromDb;
|
||||
if (conversation != null) {
|
||||
|
@ -2651,6 +2657,8 @@ public class XmppConnectionService extends Service {
|
|||
Conversation.MODE_SINGLE, counterpart);
|
||||
}
|
||||
this.databaseBackend.createConversation(conversation);
|
||||
android.util.Log.e("41fd 2 not find", jid.toString() + " " + counterpart);
|
||||
|
||||
loadMessagesFromDb = false;
|
||||
}
|
||||
final Conversation c = conversation;
|
||||
|
|
|
@ -1090,6 +1090,7 @@ public class ConversationFragment extends XmppFragment
|
|||
switch (conversation.getNextEncryption()) {
|
||||
case Message.ENCRYPTION_OTR:
|
||||
sendOtrMessage(message);
|
||||
break;
|
||||
case Message.ENCRYPTION_PGP:
|
||||
sendPgpMessage(message);
|
||||
break;
|
||||
|
@ -1406,6 +1407,11 @@ public class ConversationFragment extends XmppFragment
|
|||
final MenuItem menuVideoCall = menu.findItem(R.id.action_video_call);
|
||||
final MenuItem menuTogglePinned = menu.findItem(R.id.action_toggle_pinned);
|
||||
final MenuItem deleteCustomBg = menu.findItem(R.id.action_delete_custom_bg);
|
||||
final MenuItem startSecretChat = menu.findItem(R.id.action_start_secret_chat);
|
||||
final MenuItem encryption = menu.findItem(R.id.action_security);
|
||||
|
||||
boolean considerAsSecretChat = conversation.getMode() == Conversational.MODE_SINGLE &&
|
||||
conversation.getNextCounterpart() != null && conversation.hasPermanentCounterpart();
|
||||
|
||||
if (conversation != null) {
|
||||
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
||||
|
@ -1417,6 +1423,7 @@ public class ConversationFragment extends XmppFragment
|
|||
: R.string.channel_details);
|
||||
menuCall.setVisible(false);
|
||||
menuOngoingCall.setVisible(false);
|
||||
startSecretChat.setVisible(false);
|
||||
} else {
|
||||
menuMucParticipants.setVisible(false);
|
||||
final XmppConnectionService service =
|
||||
|
@ -1458,6 +1465,10 @@ public class ConversationFragment extends XmppFragment
|
|||
menuTogglePinned.setTitle(R.string.add_to_favorites);
|
||||
}
|
||||
|
||||
if (considerAsSecretChat) {
|
||||
encryption.setVisible(false);
|
||||
}
|
||||
|
||||
deleteCustomBg.setVisible(ChatBackgroundHelper.getBgFile(activity, conversation.getUuid()).exists());
|
||||
}
|
||||
|
||||
|
@ -2048,6 +2059,9 @@ public class ConversationFragment extends XmppFragment
|
|||
case R.id.action_archive:
|
||||
activity.xmppConnectionService.archiveConversation(conversation);
|
||||
break;
|
||||
case R.id.action_start_secret_chat:
|
||||
startOtrChat();
|
||||
break;
|
||||
case R.id.action_contact_details:
|
||||
activity.switchToContactDetails(conversation.getContact());
|
||||
break;
|
||||
|
@ -3979,11 +3993,20 @@ public class ConversationFragment extends XmppFragment
|
|||
protected void sendOtrMessage(final Message message) {
|
||||
final ConversationsActivity activity = (ConversationsActivity) getActivity();
|
||||
final XmppConnectionService xmppService = activity.xmppConnectionService;
|
||||
message.setCounterpart(conversation.getNextCounterpart());
|
||||
xmppService.sendMessage(message);
|
||||
messageSent();
|
||||
}
|
||||
|
||||
protected void startOtrChat() {
|
||||
final ConversationsActivity activity = (ConversationsActivity) getActivity();
|
||||
activity.selectPresence(conversation,
|
||||
() -> {
|
||||
message.setCounterpart(conversation.getNextCounterpart());
|
||||
xmppService.sendMessage(message);
|
||||
messageSent();
|
||||
Conversation c = activity.xmppConnectionService.findOrCreateConversation(conversation.getAccount(), conversation.getJid(), null, false, false, false, conversation.getNextCounterpart());
|
||||
conversation.setNextCounterpart(null);
|
||||
if (c != conversation) {
|
||||
activity.switchToConversation(c);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -757,8 +757,12 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
|||
if (mainFragment instanceof ConversationFragment) {
|
||||
final Conversation conversation = ((ConversationFragment) mainFragment).getConversation();
|
||||
if (conversation != null) {
|
||||
if (conversation.getNextCounterpart() != null) {
|
||||
actionBar.setTitle(getString(R.string.muc_private_conversation_title, conversation.getNextCounterpart().getResource(), conversation.getName()));
|
||||
if (conversation.getNextCounterpart() != null && conversation.hasPermanentCounterpart()) {
|
||||
if (conversation.getMode() == Conversational.MODE_MULTI) {
|
||||
actionBar.setTitle(getString(R.string.muc_private_conversation_title, conversation.getNextCounterpart().getResource(), conversation.getName()));
|
||||
} else {
|
||||
actionBar.setTitle(getString(R.string.secret_chat_title, conversation.getName(), ""));
|
||||
}
|
||||
} else {
|
||||
actionBar.setTitle(conversation.getName());
|
||||
}
|
||||
|
@ -780,7 +784,9 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
|||
} else if (conversation.getMode() == Conversation.MODE_SINGLE) {
|
||||
Contact contact = conversation.getContact();
|
||||
List<String> statuses = contact.getPresences().getStatusMessages();
|
||||
if (!statuses.isEmpty() && !statuses.get(0).isBlank()) {
|
||||
if (conversation.getNextCounterpart() != null && conversation.hasPermanentCounterpart()) {
|
||||
actionBar.setSubtitle(conversation.getNextCounterpart().getResource());
|
||||
} else if (!statuses.isEmpty() && !statuses.get(0).isBlank()) {
|
||||
actionBar.setSubtitle(statuses.get(0));
|
||||
handler.postDelayed(refreshTitleRunnable, 5000L);
|
||||
} else {
|
||||
|
|
|
@ -338,8 +338,12 @@ public class ConversationAdapter
|
|||
}
|
||||
|
||||
CharSequence name = conversation.getName();
|
||||
if (conversation.getNextCounterpart() != null) {
|
||||
name = viewHolder.binding.getRoot().getResources().getString(R.string.muc_private_conversation_title, conversation.getNextCounterpart().getResource(), conversation.getName());
|
||||
if (conversation.getNextCounterpart() != null && conversation.hasPermanentCounterpart()) {
|
||||
if (conversation.getMode() == Conversational.MODE_MULTI) {
|
||||
name = viewHolder.binding.getRoot().getResources().getString(R.string.muc_private_conversation_title, conversation.getNextCounterpart().getResource(), conversation.getName());
|
||||
} else {
|
||||
name = viewHolder.binding.getRoot().getResources().getString(R.string.secret_chat_title, conversation.getName(), conversation.getNextCounterpart().getResource());
|
||||
}
|
||||
}
|
||||
|
||||
if (conversation.withSelf()) {
|
||||
|
|
|
@ -121,6 +121,11 @@
|
|||
android:orderInCategory="60"
|
||||
android:title="@string/action_end_conversation"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/action_start_secret_chat"
|
||||
android:orderInCategory="65"
|
||||
android:title="@string/action_start_secret_chat"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:orderInCategory="70"
|
||||
android:title="@string/more_options">
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<string name="action_accounts">Manage accounts</string>
|
||||
<string name="action_account">Manage account</string>
|
||||
<string name="action_end_conversation">Close conversation</string>
|
||||
<string name="action_start_secret_chat">Start secret chat</string>
|
||||
<string name="action_contact_details">Contact details</string>
|
||||
<string name="action_muc_details">Group chat details</string>
|
||||
<string name="channel_details">Channel details</string>
|
||||
|
@ -1089,6 +1090,7 @@
|
|||
<string name="resize">resize</string>
|
||||
<string name="filter">filter</string>
|
||||
<string name="could_not_create_file">could_not_create_file</string>
|
||||
<string name="secret_chat_title">%1$s (Secret Chat / %2$s)</string>
|
||||
<string name="muc_private_conversation_title">%1$s (%2$s)</string>
|
||||
<string name="note_to_self_conversation_title">Note to self (%1$s)</string>
|
||||
<string name="muc_private_conversation_info_title">Private conversation with:</string>
|
||||
|
|
Loading…
Reference in a new issue