otr conversations implementation

This commit is contained in:
kosyak 2024-09-30 05:47:13 +02:00
parent 389074e802
commit 973a48ef62
9 changed files with 109 additions and 31 deletions

View file

@ -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);
}
}

View file

@ -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) {

View file

@ -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);

View file

@ -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;

View file

@ -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;
activity.selectPresence(conversation,
() -> {
message.setCounterpart(conversation.getNextCounterpart());
xmppService.sendMessage(message);
messageSent();
}
protected void startOtrChat() {
final ConversationsActivity activity = (ConversationsActivity) getActivity();
activity.selectPresence(conversation,
() -> {
Conversation c = activity.xmppConnectionService.findOrCreateConversation(conversation.getAccount(), conversation.getJid(), null, false, false, false, conversation.getNextCounterpart());
conversation.setNextCounterpart(null);
if (c != conversation) {
activity.switchToConversation(c);
}
});
}

View file

@ -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) {
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 {

View file

@ -338,8 +338,12 @@ public class ConversationAdapter
}
CharSequence name = conversation.getName();
if (conversation.getNextCounterpart() != null) {
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()) {

View file

@ -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">

View file

@ -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>