From 48f384b117f42e5bbe295219c2ce80de83bf7997 Mon Sep 17 00:00:00 2001 From: Arne Date: Sat, 18 Mar 2023 03:03:01 +0100 Subject: [PATCH] Support XEP-0425 message moderation (Stephen Paul Weber) + fix unhandled exception in DatabaseBackend --- monocles_chat.doap | 9 ++- .../conversations/entities/Conversation.java | 13 ++-- .../entities/IndividualMessage.java | 8 ++- .../siacs/conversations/entities/Message.java | 60 +++++++++++++++++-- .../conversations/generator/IqGenerator.java | 15 ++++- .../conversations/parser/MessageParser.java | 26 ++++++-- .../persistance/DatabaseBackend.java | 19 +++++- .../services/XmppConnectionService.java | 11 ++++ .../ui/ConversationFragment.java | 11 ++++ .../siacs/conversations/ui/XmppActivity.java | 20 ++++--- src/main/res/menu/message_context.xml | 4 ++ src/main/res/values/strings.xml | 3 + 12 files changed, 164 insertions(+), 35 deletions(-) diff --git a/monocles_chat.doap b/monocles_chat.doap index 81e939364..d1ca01514 100644 --- a/monocles_chat.doap +++ b/monocles_chat.doap @@ -452,6 +452,13 @@ 0.2.0 + + + + complete + 0.2.1 + + @@ -476,4 +483,4 @@ - \ No newline at end of file + diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 967772f14..4bfd59b3f 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -2,6 +2,7 @@ package eu.siacs.conversations.entities; import static eu.siacs.conversations.entities.Bookmark.printableValue; +import android.annotation.SuppressLint; import android.content.ContentValues; import android.content.SharedPreferences; import android.database.Cursor; @@ -128,6 +129,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl } } + @SuppressLint("Range") public static Conversation fromCursor(Cursor cursor) { return new Conversation(cursor.getString(cursor.getColumnIndex(UUID)), cursor.getString(cursor.getColumnIndex(NAME)), @@ -488,7 +490,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl return null; } - public Message findMessageWithRemoteIdAndCounterpart(String id, Jid counterpart, boolean received, boolean carbon) { + public Message findMessageWithRemoteIdAndCounterpart(String id, Jid counterpart) { synchronized (this.messages) { for (int i = this.messages.size() - 1; i >= 0; --i) { final Message message = messages.get(i); @@ -496,12 +498,9 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl if (mcp == null) { continue; } - if (mcp.equals(counterpart) && ((message.getStatus() == Message.STATUS_RECEIVED) == received) - && (carbon == message.isCarbon() || received)) { - final boolean idMatch = id.equals(message.getRemoteMsgId()) || message.remoteMsgIdMatchInEdit(id); - if (idMatch && !message.isFileOrImage() && !message.treatAsDownloadable()) { - return message; - } + if (mcp.equals(counterpart) || mcp.asBareJid().equals(counterpart)) { + final boolean idMatch = id.equals(message.getRemoteMsgId()) || message.remoteMsgIdMatchInEdit(id) || (getMode() == MODE_MULTI && id.equals(message.getServerMsgId())); + if (idMatch) return message; } } } diff --git a/src/main/java/eu/siacs/conversations/entities/IndividualMessage.java b/src/main/java/eu/siacs/conversations/entities/IndividualMessage.java index a8ecb35a3..9fcceb06a 100644 --- a/src/main/java/eu/siacs/conversations/entities/IndividualMessage.java +++ b/src/main/java/eu/siacs/conversations/entities/IndividualMessage.java @@ -28,6 +28,7 @@ */ package eu.siacs.conversations.entities; +import android.annotation.SuppressLint; import android.database.Cursor; import java.util.Set; @@ -42,7 +43,7 @@ public class IndividualMessage extends Message { } private IndividualMessage(Conversational conversation, String uuid, String conversationUUid, Jid counterpart, Jid trueCounterpart, String body, long timeSent, int encryption, int status, int type, boolean carbon, String remoteMsgId, String relativeFilePath, String serverMsgId, String fingerprint, boolean read, boolean deleted, String edited, boolean oob, String errorMessage, Set readByMarkers, boolean markable, boolean file_deleted, String bodyLanguage, String retractId) { - super(conversation, uuid, conversationUUid, counterpart, trueCounterpart, body, timeSent, encryption, status, type, carbon, remoteMsgId, relativeFilePath, serverMsgId, fingerprint, read, deleted, edited, oob, errorMessage, readByMarkers, markable, file_deleted, bodyLanguage,retractId); + super(conversation, uuid, conversationUUid, counterpart, trueCounterpart, body, timeSent, encryption, status, type, carbon, remoteMsgId, relativeFilePath, serverMsgId, fingerprint, read, deleted, edited, oob, errorMessage, readByMarkers, markable, file_deleted, bodyLanguage,retractId,null); } public static Message createDateSeparator(Message message) { @@ -53,10 +54,11 @@ public class IndividualMessage extends Message { return separator; } + @SuppressLint("Range") public static Message fromCursor(Cursor cursor, Conversational conversation) { Jid jid; try { - String value = cursor.getString(cursor.getColumnIndex(COUNTERPART)); + @SuppressLint("Range") String value = cursor.getString(cursor.getColumnIndex(COUNTERPART)); if (value != null) { jid = Jid.of(value); } else { @@ -69,7 +71,7 @@ public class IndividualMessage extends Message { } Jid trueCounterpart; try { - String value = cursor.getString(cursor.getColumnIndex(TRUE_COUNTERPART)); + @SuppressLint("Range") String value = cursor.getString(cursor.getColumnIndex(TRUE_COUNTERPART)); if (value != null) { trueCounterpart = Jid.of(value); } else { diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index e19e89176..3c8b673a0 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.entities; +import android.annotation.SuppressLint; import android.content.ContentValues; import android.database.Cursor; import android.graphics.Color; @@ -8,10 +9,12 @@ import android.util.Log; import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; +import com.google.common.io.ByteSource; import com.google.common.primitives.Longs; import org.json.JSONException; +import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Iterator; @@ -35,6 +38,9 @@ import eu.siacs.conversations.utils.MimeUtils; import eu.siacs.conversations.utils.Patterns; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.utils.XmppUri; +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xml.Tag; +import eu.siacs.conversations.xml.XmlReader; import eu.siacs.conversations.xmpp.Jid; public class Message extends AbstractEntity implements AvatarService.Avatarable { @@ -108,6 +114,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable protected boolean file_deleted = false; protected boolean carbon = false; protected boolean oob = false; + protected List payloads = new ArrayList<>(); + protected List edits = new ArrayList<>(); protected String relativeFilePath; protected boolean read = true; @@ -168,6 +176,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable false, false, null, + null, null); } @@ -195,6 +204,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable false, false, null, + null, null); } @@ -204,7 +214,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable final String remoteMsgId, final String relativeFilePath, final String serverMsgId, final String fingerprint, final boolean read, final boolean deleted, final String edited, final boolean oob, final String errorMessage, final Set readByMarkers, - final boolean markable, final boolean file_deleted, final String bodyLanguage, final String retractId) { + final boolean markable, final boolean file_deleted, final String bodyLanguage, final String retractId, final List payloads) { this.conversation = conversation; this.uuid = uuid; this.conversationUuid = conversationUUid; @@ -230,9 +240,22 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable this.file_deleted = file_deleted; this.bodyLanguage = bodyLanguage; this.retractId = retractId; + if (payloads != null) this.payloads = payloads; } - public static Message fromCursor(Cursor cursor, Conversation conversation) { + @SuppressLint("Range") + public static Message fromCursor(Cursor cursor, Conversation conversation) throws IOException { + @SuppressLint("Range") String payloadsStr = cursor.getString(cursor.getColumnIndex("payloads")); + List payloads = new ArrayList<>(); + if (payloadsStr != null) { + final XmlReader xmlReader = new XmlReader(); + xmlReader.setInputStream(ByteSource.wrap(payloadsStr.getBytes()).openStream()); + Tag tag; + while ((tag = xmlReader.readTag()) != null) { + payloads.add(xmlReader.readElement(tag)); + } + } + return new Message(conversation, cursor.getString(cursor.getColumnIndex(UUID)), cursor.getString(cursor.getColumnIndex(CONVERSATION)), @@ -257,7 +280,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable cursor.getInt(cursor.getColumnIndex(MARKABLE)) > 0, cursor.getInt(cursor.getColumnIndex(FILE_DELETED)) > 0, cursor.getString(cursor.getColumnIndex(BODY_LANGUAGE)), - cursor.getString(cursor.getColumnIndex(RETRACT_ID)) + cursor.getString(cursor.getColumnIndex(RETRACT_ID)), + payloads ); } @@ -459,7 +483,9 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable public void setFileDeleted(boolean file_deleted) { this.file_deleted = file_deleted; } - + public void setDeleted(boolean deleted) { + this.deleted = deleted; + } public void markRead() { this.read = true; } @@ -852,6 +878,18 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable public void setOob(boolean isOob) { this.oob = isOob; } + public void clearPayloads() { + this.payloads.clear(); + } + public void addPayload(Element el) { + if (el == null) return; + + this.payloads.add(el); + } + + public List getPayloads() { + return new ArrayList<>(this.payloads); + } public String getMimeType() { String extension; @@ -922,9 +960,17 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable } public synchronized void resetFileParams() { - this.fileParams = null; + if (fileParams != null && this.fileParams != null && this.fileParams.sims != null && fileParams.sims == null) { + fileParams.sims = this.fileParams.sims; + } + this.fileParams = fileParams; + } + public synchronized void setFileParams(FileParams fileParams) { + if (fileParams != null && this.fileParams != null && this.fileParams.sims != null && fileParams.sims == null) { + fileParams.sims = this.fileParams.sims; + } + this.fileParams = fileParams; } - public synchronized FileParams getFileParams() { if (fileParams == null) { fileParams = new FileParams(); @@ -1022,6 +1068,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable public int height = 0; public int runtime = 0; public String subject = ""; + public Element sims = null; + public long getSize() { return size == null ? 0 : size; } diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index f6f6cf10d..9cb845379 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -37,6 +37,8 @@ import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.pep.Avatar; import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import eu.siacs.conversations.entities.Message; + public class IqGenerator extends AbstractGenerator { @@ -407,7 +409,18 @@ public class IqGenerator extends AbstractGenerator { item.setAttribute("role", role); return packet; } - + public IqPacket moderateMessage(Account account, Message m, String reason) { + IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + packet.setTo(m.getConversation().getJid().asBareJid()); + packet.setFrom(account.getJid()); + Element moderate = + packet.addChild("apply-to", "urn:xmpp:fasten:0") + .setAttribute("id", m.getServerMsgId()) + .addChild("moderate", "urn:xmpp:message-moderate:0"); + moderate.addChild("retract", "urn:xmpp:message-retract:0"); + moderate.addChild("reason", "urn:xmpp:message-moderate:0").setContent(reason); + return packet; + } public IqPacket destroyRoom(Conversation conference) { IqPacket packet = new IqPacket(IqPacket.TYPE.SET); packet.setTo(conference.getJid().asBareJid()); diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 2fa4beaef..4c8be7169 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -496,8 +496,16 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece final Element replaceElement = packet.findChild("replace", "urn:xmpp:message-correct:0"); final Element oob = packet.findChild("x", Namespace.OOB); final String oobUrl = oob != null ? oob.findChildContent("url") : null; - final String replacementId = replaceElement == null ? null : replaceElement.getAttribute("id"); - + String replacementId = replaceElement == null ? null : replaceElement.getAttribute("id"); + boolean replaceAsRetraction = false; + if (replacementId == null) { + Element fasten = packet.findChild("apply-to", "urn:xmpp:fasten:0"); + if (fasten != null && (fasten.findChild("retract", "urn:xmpp:message-retract:0") != null || fasten.findChild("moderated", "urn:xmpp:message-moderate:0") != null)) { + replacementId = fasten.getAttribute("id"); + packet.setBody(""); + replaceAsRetraction = true; + } + } final Element applyToElement = packet.findChild("apply-to", "urn:xmpp:fasten:0"); final String retractId = applyToElement != null && applyToElement.findChild("retract", "urn:xmpp:message-retract:0") != null ? applyToElement.getAttribute("id") : null; @@ -726,9 +734,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece if (replacementId != null && mXmppConnectionService.allowMessageCorrection()) { Message replacedMessage = conversation.findMessageWithRemoteIdAndCounterpart(replacementId, - counterpart, - message.getStatus() == Message.STATUS_RECEIVED, - message.isCarbon()); + counterpart + ); if (replacedMessage == null) { replacedMessage = conversation.findSentMessageWithUuidOrRemoteId(replacementId, true, true); @@ -741,7 +748,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece && replacedMessage.getTrueCounterpart().asBareJid().equals(message.getTrueCounterpart().asBareJid()); final boolean mucUserMatches = query == null && replacedMessage.sameMucUser(message); //can not be checked when using mam final boolean duplicate = conversation.hasDuplicateMessage(message); - if (fingerprintsMatch && (trueCountersMatch || !conversationMultiMode || mucUserMatches) && !duplicate) { + if (fingerprintsMatch && (trueCountersMatch || !conversationMultiMode || mucUserMatches || counterpart.isBareJid()) && !duplicate) { Log.d(Config.LOGTAG, "replaced message '" + replacedMessage.getBody() + "' with '" + message.getBody() + "'"); synchronized (replacedMessage) { final String uuid = replacedMessage.getUuid(); @@ -751,6 +758,13 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece replacedMessage.setUuid(UUID.randomUUID().toString()); replacedMessage.setBody(message.getBody()); replacedMessage.setRemoteMsgId(remoteMsgId); + if (replaceAsRetraction) { + mXmppConnectionService.getFileBackend().deleteFile(replacedMessage); + mXmppConnectionService.evictPreview(message.getUuid()); + replacedMessage.clearPayloads(); + replacedMessage.setFileParams(null); + replacedMessage.setDeleted(true); + } if (replacedMessage.getServerMsgId() == null || message.getServerMsgId() != null) { replacedMessage.setServerMsgId(message.getServerMsgId()); } diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 66e18476a..c838e88cf 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -2,6 +2,7 @@ package eu.siacs.conversations.persistance; import static eu.siacs.conversations.ui.util.UpdateHelper.moveData_PAM_monocles; +import android.annotation.SuppressLint; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; @@ -702,6 +703,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { return isExist; } + @SuppressLint("Range") private void canonicalizeJids(SQLiteDatabase db) { // migrate db to new, canonicalized JID domainpart representation @@ -956,7 +958,12 @@ public class DatabaseBackend extends SQLiteOpenHelper { @Override public Message next() { - Message message = Message.fromCursor(cursor, conversation); + Message message = null; + try { + message = Message.fromCursor(cursor, conversation); + } catch (IOException e) { + throw new RuntimeException(e); + } cursor.moveToNext(); return message; } @@ -1345,6 +1352,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { null, null, null); } + @SuppressLint("Range") public SessionRecord loadSession(Account account, SignalProtocolAddress contact) { SessionRecord session = null; Cursor cursor = getCursorForSession(account, contact); @@ -1366,6 +1374,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { return getSubDeviceSessions(db, account, contact); } + @SuppressLint("Range") private List getSubDeviceSessions(SQLiteDatabase db, Account account, SignalProtocolAddress contact) { List devices = new ArrayList<>(); String[] columns = {SQLiteAxolotlStore.DEVICE_ID}; @@ -1460,6 +1469,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { return cursor; } + @SuppressLint("Range") public PreKeyRecord loadPreKey(Account account, int preKeyId) { PreKeyRecord record = null; Cursor cursor = getCursorForPreKey(account, preKeyId); @@ -1513,6 +1523,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { return cursor; } + @SuppressLint("Range") public SignedPreKeyRecord loadSignedPreKey(Account account, int signedPreKeyId) { SignedPreKeyRecord record = null; Cursor cursor = getCursorForSignedPreKey(account, signedPreKeyId); @@ -1528,6 +1539,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { return record; } + @SuppressLint("Range") public List loadSignedPreKeys(Account account) { List prekeys = new ArrayList<>(); SQLiteDatabase db = this.getReadableDatabase(); @@ -1645,6 +1657,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { return loadOwnIdentityKeyPair(db, account); } + @SuppressLint("Range") private IdentityKeyPair loadOwnIdentityKeyPair(SQLiteDatabase db, Account account) { String name = account.getJid().asBareJid().toString(); IdentityKeyPair identityKeyPair = null; @@ -1675,7 +1688,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { continue; } try { - String key = cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)); + @SuppressLint("Range") String key = cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)); if (key != null) { identityKeys.add(new IdentityKey(Base64.decode(key, Base64.DEFAULT), 0)); } else { @@ -1798,7 +1811,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { return null; } else { cursor.moveToFirst(); - byte[] certificate = cursor.getBlob(cursor.getColumnIndex(SQLiteAxolotlStore.CERTIFICATE)); + @SuppressLint("Range") byte[] certificate = cursor.getBlob(cursor.getColumnIndex(SQLiteAxolotlStore.CERTIFICATE)); cursor.close(); if (certificate == null || certificate.length == 0) { return null; diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 75f849ff0..0c95ac757 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -3933,6 +3933,17 @@ public class XmppConnectionService extends Service { } }); } + public void moderateMessage(final Account account, final Message m, final String reason) { + IqPacket request = this.mIqGenerator.moderateMessage(account, m, reason); + Log.d(Config.LOGTAG, "moderate: " + request); + sendIqPacket(account, request, (a, packet) -> { + Log.d(Config.LOGTAG, "moderate1: " + packet); + if (packet.getType() != IqPacket.TYPE.RESULT) { + showErrorToastInUi(R.string.unable_to_moderate); + Log.d(Config.LOGTAG, a.getJid().asBareJid() + " unable to moderate: " + packet); + } + }); + } public void destroyRoom(final Conversation conversation, final OnRoomDestroy callback) { try { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index b6909264f..138e15811 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -1482,6 +1482,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke MenuItem quoteMessage = menu.findItem(R.id.quote_message); MenuItem retryDecryption = menu.findItem(R.id.retry_decryption); MenuItem correctMessage = menu.findItem(R.id.correct_message); + MenuItem moderateMessage = menu.findItem(R.id.moderate_message); MenuItem deleteMessage = menu.findItem(R.id.delete_message); MenuItem shareWith = menu.findItem(R.id.share_with); MenuItem sendAgain = menu.findItem(R.id.send_again); @@ -1512,6 +1513,9 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke && m.getConversation() instanceof Conversation) { correctMessage.setVisible(true); } + if (conversation.getMode() == Conversation.MODE_MULTI && conversation.getMucOptions().getSelf().getRole().ranks(MucOptions.Role.MODERATOR) && conversation.getMucOptions().hasFeature("urn:xmpp:message-moderate:0")) { + moderateMessage.setVisible(true); + } if ((m.isFileOrImage() && !fileDeleted && !receiving) || (m.getType() == Message.TYPE_TEXT && !m.treatAsDownloadable()) && !unInitiatedButKnownSize && t == null && !messageDeleted) { shareWith.setVisible(true); @@ -1586,6 +1590,12 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke case R.id.correct_message: correctMessage(selectedMessage); return true; + case R.id.moderate_message: + activity.quickEdit("Spam", (reason) -> { + activity.xmppConnectionService.moderateMessage(conversation.getAccount(), selectedMessage, reason); + return null; + }, R.string.moderate_reason, false, false, true); + return true; case R.id.copy_message: ShareUtil.copyToClipboard(activity, selectedMessage); return true; @@ -3004,6 +3014,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke return true; } + @SuppressLint("StringFormatInvalid") private void updateSnackBar(final Conversation conversation) { if (conversation == null) { return; diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index d65e7f6da..ffd5ea6f4 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -298,6 +298,7 @@ public abstract class XmppActivity extends ActionBarActivity { return xmppConnectionService.getPgpEngine() != null; } + @SuppressLint("StringFormatInvalid") public void showInstallPgpDialog() { final AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(getString(R.string.openkeychain_required)); @@ -887,13 +888,16 @@ public abstract class XmppActivity extends ActionBarActivity { protected void quickPasswordEdit(String previousValue, OnValueEdited callback) { quickEdit(previousValue, callback, R.string.password, true, false); } - + protected void quickEdit(final String previousValue, final OnValueEdited callback, final @StringRes int hint, boolean password, boolean permitEmpty) { + quickEdit(previousValue, callback, hint, password, permitEmpty, false); + } @SuppressLint("InflateParams") - private void quickEdit(final String previousValue, - final OnValueEdited callback, - final @StringRes int hint, - boolean password, - boolean permitEmpty) { + void quickEdit(final String previousValue, + final OnValueEdited callback, + final @StringRes int hint, + boolean password, + boolean permitEmpty, + boolean alwaysCallback) { final AlertDialog.Builder builder = new AlertDialog.Builder(this); DialogQuickeditBinding binding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.dialog_quickedit, null, false); if (password) { @@ -914,7 +918,7 @@ public abstract class XmppActivity extends ActionBarActivity { dialog.show(); View.OnClickListener clickListener = v -> { String value = binding.inputEditText.getText().toString(); - if (!value.equals(previousValue) && (!value.trim().isEmpty() || permitEmpty)) { + if ((alwaysCallback || !value.equals(previousValue)) && (!value.trim().isEmpty() || permitEmpty)) { String error = callback.onValueEdited(value); if (error != null) { binding.inputLayout.setError(error); @@ -1057,7 +1061,7 @@ public abstract class XmppActivity extends ActionBarActivity { inviteURL = Config.inviteUserURL + user + "/" + domain; } Log.d(Config.LOGTAG, "Invite uri = " + inviteURL); - final String inviteText = getString(R.string.InviteText, user); + @SuppressLint({"StringFormatInvalid", "LocalSuppress"}) final String inviteText = getString(R.string.InviteText, user); final Intent intent = new Intent(android.content.Intent.ACTION_SEND); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_SUBJECT, user + " " + getString(R.string.inviteUser_Subject) + " " + getString(R.string.app_name)); diff --git a/src/main/res/menu/message_context.xml b/src/main/res/menu/message_context.xml index 14e81b698..c3c7d0b26 100644 --- a/src/main/res/menu/message_context.xml +++ b/src/main/res/menu/message_context.xml @@ -29,6 +29,10 @@ android:id="@+id/delete_message" android:title="@string/delete_message" android:visible="false" /> + A user-chosen push server to relay push messages via XMPP to your device. None (deactivated) Custom + Moderate + Moderation Resaon + Unable to Moderate