diff --git a/fastlane/metadata/android/it-IT/changelogs/4214204.txt b/fastlane/metadata/android/it-IT/changelogs/4214204.txt new file mode 100644 index 0000000000000000000000000000000000000000..906a67ca8dc814e81241e3998fa8fa9474956a3d --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/4214204.txt @@ -0,0 +1,2 @@ +* Supporto a 'Stato di interruzione del servizio' +* Correzioni di sicurezza minori nella lettura di corpi multipli, id-occupanti e id-stanze diff --git a/fastlane/metadata/android/pl-PL/changelogs/4214204.txt b/fastlane/metadata/android/pl-PL/changelogs/4214204.txt new file mode 100644 index 0000000000000000000000000000000000000000..d10e24b966d13858cb9efe93c0ce8b30b4c5c2d4 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/4214204.txt @@ -0,0 +1,2 @@ +* Obsługa statusu niedostępności usługi +* Drobne poprawki bezpieczeństwa parsowania wielu elementów treści, identyfikatorów uczestników i stanz diff --git a/src/conversations/res/values-ga/strings.xml b/src/conversations/res/values-ga/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..ee21d93fd943365566a149cdeee1e54576395699 --- /dev/null +++ b/src/conversations/res/values-ga/strings.xml @@ -0,0 +1,6 @@ + + + Roghnaigh do freastalaí XMPP + Bain úsáid as conversations.im + Oscail cuntas nua + diff --git a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java index d3588a9952cc22cb0e5cb795181c7d09dda08f5f..e0ba84c73a08a9e84dd704279675514e34ecfba8 100644 --- a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java +++ b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java @@ -3,27 +3,10 @@ package eu.siacs.conversations.crypto; import android.app.PendingIntent; import android.content.Intent; import android.util.Log; - import androidx.annotation.StringRes; - import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.google.common.base.Strings; - -import org.openintents.openpgp.OpenPgpError; -import org.openintents.openpgp.OpenPgpSignatureResult; -import org.openintents.openpgp.util.OpenPgpApi; -import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; @@ -35,6 +18,18 @@ import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.UiCallback; import eu.siacs.conversations.utils.AsciiArmor; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import org.openintents.openpgp.OpenPgpError; +import org.openintents.openpgp.OpenPgpSignatureResult; +import org.openintents.openpgp.util.OpenPgpApi; +import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback; public class PgpEngine { private final OpenPgpApi api; @@ -48,9 +43,20 @@ public class PgpEngine { private static void logError(Account account, OpenPgpError error) { if (error != null) { error.describeContents(); - Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": OpenKeychain error '" + error.getMessage() + "' code=" + error.getErrorId() + " class=" + error.getClass().getName()); + Log.d( + Config.LOGTAG, + account.getJid().asBareJid().toString() + + ": OpenKeychain error '" + + error.getMessage() + + "' code=" + + error.getErrorId() + + " class=" + + error.getClass().getName()); } else { - Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": OpenKeychain error with no message"); + Log.d( + Config.LOGTAG, + account.getJid().asBareJid().toString() + + ": OpenKeychain error with no message"); } } @@ -60,8 +66,7 @@ public class PgpEngine { final Conversation conversation = (Conversation) message.getConversation(); if (conversation.getMode() == Conversation.MODE_SINGLE) { long[] keys = { - conversation.getContact().getPgpKeyId(), - conversation.getAccount().getPgpId() + conversation.getContact().getPgpKeyId(), conversation.getAccount().getPgpId() }; params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys); } else { @@ -78,75 +83,95 @@ public class PgpEngine { } InputStream is = new ByteArrayInputStream(body.getBytes()); final OutputStream os = new ByteArrayOutputStream(); - api.executeApiAsync(params, is, os, result -> { - switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { - case OpenPgpApi.RESULT_CODE_SUCCESS: - try { - os.flush(); - final ArrayList encryptedMessageBody = new ArrayList<>(); - final String[] lines = os.toString().split("\n"); - for (int i = 2; i < lines.length - 1; ++i) { - if (!lines[i].contains("Version")) { - encryptedMessageBody.add(lines[i].trim()); + api.executeApiAsync( + params, + is, + os, + result -> { + switch (result.getIntExtra( + OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { + case OpenPgpApi.RESULT_CODE_SUCCESS: + try { + os.flush(); + final ArrayList encryptedMessageBody = + new ArrayList<>(); + final String[] lines = os.toString().split("\n"); + for (int i = 2; i < lines.length - 1; ++i) { + if (!lines[i].contains("Version")) { + encryptedMessageBody.add(lines[i].trim()); + } + } + message.setEncryptedBody( + Joiner.on('\n').join(encryptedMessageBody)); + message.setEncryption(Message.ENCRYPTION_DECRYPTED); + mXmppConnectionService.sendMessage(message); + callback.success(message); + } catch (IOException e) { + callback.error(R.string.openpgp_error, message); } - } - message.setEncryptedBody(Joiner.on('\n').join(encryptedMessageBody)); - message.setEncryption(Message.ENCRYPTION_DECRYPTED); - mXmppConnectionService.sendMessage(message); - callback.success(message); - } catch (IOException e) { - callback.error(R.string.openpgp_error, message); - } - break; - case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: - callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), message); - break; - case OpenPgpApi.RESULT_CODE_ERROR: - OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR); - String errorMessage = error != null ? error.getMessage() : null; - @StringRes final int res; - if (errorMessage != null && errorMessage.startsWith("Bad key for encryption")) { - res = R.string.bad_key_for_encryption; - } else { - res = R.string.openpgp_error; + break; + case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: + callback.userInputRequired( + result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), + message); + break; + case OpenPgpApi.RESULT_CODE_ERROR: + OpenPgpError error = + result.getParcelableExtra(OpenPgpApi.RESULT_ERROR); + String errorMessage = error != null ? error.getMessage() : null; + @StringRes final int res; + if (errorMessage != null + && errorMessage.startsWith("Bad key for encryption")) { + res = R.string.bad_key_for_encryption; + } else { + res = R.string.openpgp_error; + } + logError(conversation.getAccount(), error); + callback.error(res, message); + break; } - logError(conversation.getAccount(), error); - callback.error(res, message); - break; - } - }); + }); } else { try { - DownloadableFile inputFile = this.mXmppConnectionService - .getFileBackend().getFile(message, true); - DownloadableFile outputFile = this.mXmppConnectionService - .getFileBackend().getFile(message, false); + DownloadableFile inputFile = + this.mXmppConnectionService.getFileBackend().getFile(message, true); + DownloadableFile outputFile = + this.mXmppConnectionService.getFileBackend().getFile(message, false); outputFile.getParentFile().mkdirs(); outputFile.createNewFile(); final InputStream is = new FileInputStream(inputFile); final OutputStream os = new FileOutputStream(outputFile); - api.executeApiAsync(params, is, os, result -> { - switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { - case OpenPgpApi.RESULT_CODE_SUCCESS: - try { - os.flush(); - } catch (IOException ignored) { - //ignored + api.executeApiAsync( + params, + is, + os, + result -> { + switch (result.getIntExtra( + OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { + case OpenPgpApi.RESULT_CODE_SUCCESS: + try { + os.flush(); + } catch (IOException ignored) { + // ignored + } + FileBackend.close(os); + mXmppConnectionService.sendMessage(message); + callback.success(message); + break; + case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: + callback.userInputRequired( + result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), + message); + break; + case OpenPgpApi.RESULT_CODE_ERROR: + logError( + conversation.getAccount(), + result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); + callback.error(R.string.openpgp_error, message); + break; } - FileBackend.close(os); - mXmppConnectionService.sendMessage(message); - callback.success(message); - break; - case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: - callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), message); - break; - case OpenPgpApi.RESULT_CODE_ERROR: - logError(conversation.getAccount(), result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); - callback.error(R.string.openpgp_error, message); - break; - } - }); + }); } catch (final IOException e) { callback.error(R.string.openpgp_error, message); } @@ -168,11 +193,11 @@ public class PgpEngine { final InputStream is = new ByteArrayInputStream(Strings.nullToEmpty(status).getBytes()); final ByteArrayOutputStream os = new ByteArrayOutputStream(); final Intent result = api.executeApi(params, is, os); - switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, - OpenPgpApi.RESULT_CODE_ERROR)) { + switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { case OpenPgpApi.RESULT_CODE_SUCCESS: - final OpenPgpSignatureResult sigResult = result.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE); - //TODO unsure that sigResult.getResult() is either 1, 2 or 3 + final OpenPgpSignatureResult sigResult = + result.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE); + // TODO unsure that sigResult.getResult() is either 1, 2 or 3 if (sigResult != null) { return sigResult.getKeyId(); } else { @@ -190,22 +215,31 @@ public class PgpEngine { public void chooseKey(final Account account, final UiCallback callback) { Intent p = new Intent(); p.setAction(OpenPgpApi.ACTION_GET_SIGN_KEY_ID); - api.executeApiAsync(p, null, null, result -> { - switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { - case OpenPgpApi.RESULT_CODE_SUCCESS: - callback.success(account); - return; - case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: - callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), account); - return; - case OpenPgpApi.RESULT_CODE_ERROR: - logError(account, result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); - callback.error(R.string.openpgp_error, account); - } - }); + api.executeApiAsync( + p, + null, + null, + result -> { + switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { + case OpenPgpApi.RESULT_CODE_SUCCESS: + callback.success(account); + return; + case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: + callback.userInputRequired( + result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), account); + return; + case OpenPgpApi.RESULT_CODE_ERROR: + logError(account, result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); + callback.error(R.string.openpgp_error, account); + } + }); } - public void generateSignature(Intent intent, final Account account, String status, final UiCallback callback) { + public void generateSignature( + Intent intent, + final Account account, + String status, + final UiCallback callback) { if (account.getPgpId() == 0) { return; } @@ -215,70 +249,86 @@ public class PgpEngine { params.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, account.getPgpId()); InputStream is = new ByteArrayInputStream(status.getBytes()); final OutputStream os = new ByteArrayOutputStream(); - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": signing status message \"" + status + "\""); - api.executeApiAsync(params, is, os, result -> { - switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { - case OpenPgpApi.RESULT_CODE_SUCCESS: - final ArrayList signature = new ArrayList<>(); - try { - os.flush(); - boolean sig = false; - for (final String line : Splitter.on('\n').split(os.toString())) { - if (sig) { - if (line.contains("END PGP SIGNATURE")) { - sig = false; - } else { - if (!line.contains("Version")) { - signature.add(line.trim()); + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + ": signing status message \"" + status + "\""); + api.executeApiAsync( + params, + is, + os, + result -> { + switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { + case OpenPgpApi.RESULT_CODE_SUCCESS: + final ArrayList signature = new ArrayList<>(); + try { + os.flush(); + boolean sig = false; + for (final String line : Splitter.on('\n').split(os.toString())) { + if (sig) { + if (line.contains("END PGP SIGNATURE")) { + sig = false; + } else { + if (!line.contains("Version")) { + signature.add(line.trim()); + } + } + } + if (line.contains("BEGIN PGP SIGNATURE")) { + sig = true; } } + } catch (IOException e) { + callback.error(R.string.openpgp_error, null); + return; } - if (line.contains("BEGIN PGP SIGNATURE")) { - sig = true; + callback.success(Joiner.on('\n').join(signature)); + return; + case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: + callback.userInputRequired( + result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), status); + return; + case OpenPgpApi.RESULT_CODE_ERROR: + OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR); + if (error != null + && "signing subkey not found!".equals(error.getMessage())) { + callback.error(0, null); + } else { + logError(account, error); + callback.error(R.string.unable_to_connect_to_keychain, null); } - } - } catch (IOException e) { - callback.error(R.string.openpgp_error, null); - return; } - callback.success(Joiner.on('\n').join(signature)); - return; - case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: - callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), status); - return; - case OpenPgpApi.RESULT_CODE_ERROR: - OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR); - if (error != null && "signing subkey not found!".equals(error.getMessage())) { - callback.error(0, null); - } else { - logError(account, error); - callback.error(R.string.unable_to_connect_to_keychain, null); - } - } - }); + }); } public void hasKey(final Contact contact, final UiCallback callback) { Intent params = new Intent(); params.setAction(OpenPgpApi.ACTION_GET_KEY); params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId()); - api.executeApiAsync(params, null, null, new IOpenPgpCallback() { + api.executeApiAsync( + params, + null, + null, + new IOpenPgpCallback() { - @Override - public void onReturn(Intent result) { - switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { - case OpenPgpApi.RESULT_CODE_SUCCESS: - callback.success(contact); - return; - case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: - callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), contact); - return; - case OpenPgpApi.RESULT_CODE_ERROR: - logError(contact.getAccount(), result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); - callback.error(R.string.openpgp_error, contact); - } - } - }); + @Override + public void onReturn(Intent result) { + switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { + case OpenPgpApi.RESULT_CODE_SUCCESS: + callback.success(contact); + return; + case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: + callback.userInputRequired( + result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), + contact); + return; + case OpenPgpApi.RESULT_CODE_ERROR: + logError( + contact.getAccount(), + result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); + callback.error(R.string.openpgp_error, contact); + } + } + }); } public PendingIntent getIntentForKey(long pgpKeyId) { @@ -288,6 +338,6 @@ public class PgpEngine { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[0]); Intent result = api.executeApi(params, inputStream, outputStream); - return (PendingIntent) result.getParcelableExtra(OpenPgpApi.RESULT_INTENT); + return result.getParcelableExtra(OpenPgpApi.RESULT_INTENT); } } 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 078661617a806379c8df46fd36bc5f8483f741f5..fcc140ee9b6c28e80b532be999f6f1cf9752b383 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -951,7 +951,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " - + response.toString()); + + response); } pepBroken = true; } @@ -1416,7 +1416,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " - + address.toString() + + address + ", adding to cache..."); XmppAxolotlSession session = new XmppAxolotlSession( @@ -1462,7 +1462,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " - + address.toString() + + address + ", adding to cache..."); XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey); @@ -1538,7 +1538,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already fetching bundle for " - + address.toString()); + + address); } } diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java index 790e33bd65d764c21faa24d730720d53fcfdaf53..cb57be4719d0072efac7eadd9737170ec776ac29 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -2,7 +2,10 @@ package eu.siacs.conversations.crypto.axolotl; import android.util.Base64; import android.util.Log; - +import eu.siacs.conversations.Config; +import eu.siacs.conversations.utils.Compatibility; +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.Jid; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -10,7 +13,6 @@ import java.security.NoSuchProviderException; import java.security.SecureRandom; import java.util.ArrayList; import java.util.List; - import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; @@ -20,11 +22,6 @@ import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; -import eu.siacs.conversations.Config; -import eu.siacs.conversations.utils.Compatibility; -import eu.siacs.conversations.xml.Element; -import eu.siacs.conversations.xmpp.Jid; - public class XmppAxolotlMessage { public static final String CONTAINERTAG = "encrypted"; private static final String HEADER = "header"; @@ -45,7 +42,8 @@ public class XmppAxolotlMessage { private byte[] authtagPlusInnerKey = null; private byte[] iv = null; - private XmppAxolotlMessage(final Element axolotlMessage, final Jid from) throws IllegalArgumentException { + private XmppAxolotlMessage(final Element axolotlMessage, final Jid from) + throws IllegalArgumentException { this.from = from; Element header = axolotlMessage.findChild(HEADER); try { @@ -62,7 +60,8 @@ public class XmppAxolotlMessage { int recipientId = Integer.parseInt(keyElement.getAttribute(REMOTEID)); byte[] key = Base64.decode(keyElement.getContent().trim(), Base64.DEFAULT); boolean isPreKey = keyElement.getAttributeAsBoolean("prekey"); - this.keys.add(new XmppAxolotlSession.AxolotlKey(recipientId, key, isPreKey)); + this.keys.add( + new XmppAxolotlSession.AxolotlKey(recipientId, key, isPreKey)); } catch (NumberFormatException e) { throw new IllegalArgumentException("invalid remote id"); } @@ -74,11 +73,12 @@ public class XmppAxolotlMessage { iv = Base64.decode(keyElement.getContent().trim(), Base64.DEFAULT); break; default: - Log.w(Config.LOGTAG, "Unexpected element in header: " + keyElement.toString()); + Log.w(Config.LOGTAG, "Unexpected element in header: " + keyElement); break; } } - final Element payloadElement = axolotlMessage.findChildEnsureSingle(PAYLOAD, AxolotlService.PEP_PREFIX); + final Element payloadElement = + axolotlMessage.findChildEnsureSingle(PAYLOAD, AxolotlService.PEP_PREFIX); if (payloadElement != null) { ciphertext = Base64.decode(payloadElement.getContent().trim(), Base64.DEFAULT); } @@ -151,9 +151,16 @@ public class XmppAxolotlMessage { try { SecretKey secretKey = new SecretKeySpec(innerKey, KEYTYPE); IvParameterSpec ivSpec = new IvParameterSpec(iv); - Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); + Cipher cipher = + Compatibility.twentyEight() + ? Cipher.getInstance(CIPHERMODE) + : Cipher.getInstance(CIPHERMODE, PROVIDER); cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); - this.ciphertext = cipher.doFinal(Config.OMEMO_PADDING ? getPaddedBytes(plaintext) : plaintext.getBytes()); + this.ciphertext = + cipher.doFinal( + Config.OMEMO_PADDING + ? getPaddedBytes(plaintext) + : plaintext.getBytes()); if (Config.PUT_AUTH_TAG_INTO_KEY && this.ciphertext != null) { this.authtagPlusInnerKey = new byte[16 + 16]; byte[] ciphertext = new byte[this.ciphertext.length - 16]; @@ -162,8 +169,12 @@ public class XmppAxolotlMessage { System.arraycopy(this.innerKey, 0, authtagPlusInnerKey, 0, this.innerKey.length); this.ciphertext = ciphertext; } - } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException - | IllegalBlockSizeException | BadPaddingException | NoSuchProviderException + } catch (NoSuchAlgorithmException + | NoSuchPaddingException + | InvalidKeyException + | IllegalBlockSizeException + | BadPaddingException + | NoSuchProviderException | InvalidAlgorithmParameterException e) { throw new CryptoFailedException(e); } @@ -222,7 +233,8 @@ public class XmppAxolotlMessage { return encryptionElement; } - private byte[] unpackKey(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException { + private byte[] unpackKey(XmppAxolotlSession session, Integer sourceDeviceId) + throws CryptoFailedException { ArrayList possibleKeys = new ArrayList<>(); for (XmppAxolotlSession.AxolotlKey key : keys) { if (key.deviceId == sourceDeviceId) { @@ -235,17 +247,22 @@ public class XmppAxolotlMessage { return session.processReceiving(possibleKeys); } - XmppAxolotlKeyTransportMessage getParameters(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException { - return new XmppAxolotlKeyTransportMessage(session.getFingerprint(), unpackKey(session, sourceDeviceId), getIV()); + XmppAxolotlKeyTransportMessage getParameters(XmppAxolotlSession session, Integer sourceDeviceId) + throws CryptoFailedException { + return new XmppAxolotlKeyTransportMessage( + session.getFingerprint(), unpackKey(session, sourceDeviceId), getIV()); } - public XmppAxolotlPlaintextMessage decrypt(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException { + public XmppAxolotlPlaintextMessage decrypt(XmppAxolotlSession session, Integer sourceDeviceId) + throws CryptoFailedException { XmppAxolotlPlaintextMessage plaintextMessage = null; byte[] key = unpackKey(session, sourceDeviceId); if (key != null) { try { if (key.length < 32) { - throw new OutdatedSenderException("Key did not contain auth tag. Sender needs to update their OMEMO client"); + throw new OutdatedSenderException( + "Key did not contain auth tag. Sender needs to update their OMEMO" + + " client"); } final int authTagLength = key.length - 16; byte[] newCipherText = new byte[key.length - 16 + ciphertext.length]; @@ -256,18 +273,28 @@ public class XmppAxolotlMessage { ciphertext = newCipherText; key = newKey; - final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); + final Cipher cipher = + Compatibility.twentyEight() + ? Cipher.getInstance(CIPHERMODE) + : Cipher.getInstance(CIPHERMODE, PROVIDER); SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); IvParameterSpec ivSpec = new IvParameterSpec(iv); cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); String plaintext = new String(cipher.doFinal(ciphertext)); - plaintextMessage = new XmppAxolotlPlaintextMessage(Config.OMEMO_PADDING ? plaintext.trim() : plaintext, session.getFingerprint()); - - } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException - | InvalidAlgorithmParameterException | IllegalBlockSizeException - | BadPaddingException | NoSuchProviderException e) { + plaintextMessage = + new XmppAxolotlPlaintextMessage( + Config.OMEMO_PADDING ? plaintext.trim() : plaintext, + session.getFingerprint()); + + } catch (NoSuchAlgorithmException + | NoSuchPaddingException + | InvalidKeyException + | InvalidAlgorithmParameterException + | IllegalBlockSizeException + | BadPaddingException + | NoSuchProviderException e) { throw new CryptoFailedException(e); } } @@ -287,7 +314,6 @@ public class XmppAxolotlMessage { return plaintext; } - public String getFingerprint() { return fingerprint; } diff --git a/src/main/java/eu/siacs/conversations/entities/Edit.java b/src/main/java/eu/siacs/conversations/entities/Edit.java index a3865bdd08c808b10bd0d10968f6e724a15092d3..2357dc0105db72f4156da8f13c6e55fe9b4079f6 100644 --- a/src/main/java/eu/siacs/conversations/entities/Edit.java +++ b/src/main/java/eu/siacs/conversations/entities/Edit.java @@ -1,12 +1,12 @@ package eu.siacs.conversations.entities; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import java.util.ArrayList; -import java.util.List; - public class Edit { private final String editedId; @@ -45,7 +45,8 @@ public class Edit { private static Edit fromJson(JSONObject jsonObject) throws JSONException { String edited = jsonObject.has("edited_id") ? jsonObject.getString("edited_id") : null; - String serverMsgId = jsonObject.has("server_msg_id") ? jsonObject.getString("server_msg_id") : null; + String serverMsgId = + jsonObject.has("server_msg_id") ? jsonObject.getString("server_msg_id") : null; return new Edit(edited, serverMsgId); } @@ -83,9 +84,8 @@ public class Edit { Edit edit = (Edit) o; - if (editedId != null ? !editedId.equals(edit.editedId) : edit.editedId != null) - return false; - return serverMsgId != null ? serverMsgId.equals(edit.serverMsgId) : edit.serverMsgId == null; + if (!Objects.equals(editedId, edit.editedId)) return false; + return Objects.equals(serverMsgId, edit.serverMsgId); } @Override diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index 2643b8d8dcb5a32ce93b443fbdbbfe2d6ab7ce33..7c432f545f8e240b8faf87eb80b9f2dd11d984a8 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -33,6 +33,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.Set; public class MucOptions { @@ -1094,9 +1095,8 @@ public class MucOptions { if (role != user.role) return false; if (affiliation != user.affiliation) return false; - if (realJid != null ? !realJid.equals(user.realJid) : user.realJid != null) - return false; - return fullJid != null ? fullJid.equals(user.fullJid) : user.fullJid == null; + if (!Objects.equals(realJid, user.realJid)) return false; + return Objects.equals(fullJid, user.fullJid); } public boolean isDomain() { diff --git a/src/main/java/eu/siacs/conversations/entities/PresenceTemplate.java b/src/main/java/eu/siacs/conversations/entities/PresenceTemplate.java index 91741e674aaf93f1219ac4056f93e19adb6a754c..958891d34d4b8915fdc3309a4827c37b41001347 100644 --- a/src/main/java/eu/siacs/conversations/entities/PresenceTemplate.java +++ b/src/main/java/eu/siacs/conversations/entities/PresenceTemplate.java @@ -2,80 +2,77 @@ package eu.siacs.conversations.entities; import android.content.ContentValues; import android.database.Cursor; - +import java.util.Objects; public class PresenceTemplate extends AbstractEntity { - public static final String TABELNAME = "presence_templates"; - public static final String LAST_USED = "last_used"; - public static final String MESSAGE = "message"; - public static final String STATUS = "status"; - - private long lastUsed = 0; - private String statusMessage; - private Presence.Status status = Presence.Status.ONLINE; - - public PresenceTemplate(Presence.Status status, String statusMessage) { - this.status = status; - this.statusMessage = statusMessage; - this.lastUsed = System.currentTimeMillis(); - this.uuid = java.util.UUID.randomUUID().toString(); - } - - private PresenceTemplate() { - - } - - @Override - public ContentValues getContentValues() { - final String show = status.toShowString(); - ContentValues values = new ContentValues(); - values.put(LAST_USED, lastUsed); - values.put(MESSAGE, statusMessage); - values.put(STATUS, show == null ? "" : show); - values.put(UUID, uuid); - return values; - } - - public static PresenceTemplate fromCursor(Cursor cursor) { - PresenceTemplate template = new PresenceTemplate(); - template.uuid = cursor.getString(cursor.getColumnIndex(UUID)); - template.lastUsed = cursor.getLong(cursor.getColumnIndex(LAST_USED)); - template.statusMessage = cursor.getString(cursor.getColumnIndex(MESSAGE)); - template.status = Presence.Status.fromShowString(cursor.getString(cursor.getColumnIndex(STATUS))); - return template; - } - - public Presence.Status getStatus() { - return status; - } - - public String getStatusMessage() { - return statusMessage; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - PresenceTemplate template = (PresenceTemplate) o; - - if (statusMessage != null ? !statusMessage.equals(template.statusMessage) : template.statusMessage != null) - return false; - return status == template.status; - - } - - @Override - public int hashCode() { - int result = statusMessage != null ? statusMessage.hashCode() : 0; - result = 31 * result + status.hashCode(); - return result; - } - - @Override - public String toString() { - return statusMessage; - } + public static final String TABELNAME = "presence_templates"; + public static final String LAST_USED = "last_used"; + public static final String MESSAGE = "message"; + public static final String STATUS = "status"; + + private long lastUsed = 0; + private String statusMessage; + private Presence.Status status = Presence.Status.ONLINE; + + public PresenceTemplate(Presence.Status status, String statusMessage) { + this.status = status; + this.statusMessage = statusMessage; + this.lastUsed = System.currentTimeMillis(); + this.uuid = java.util.UUID.randomUUID().toString(); + } + + private PresenceTemplate() {} + + @Override + public ContentValues getContentValues() { + final String show = status.toShowString(); + ContentValues values = new ContentValues(); + values.put(LAST_USED, lastUsed); + values.put(MESSAGE, statusMessage); + values.put(STATUS, show == null ? "" : show); + values.put(UUID, uuid); + return values; + } + + public static PresenceTemplate fromCursor(Cursor cursor) { + PresenceTemplate template = new PresenceTemplate(); + template.uuid = cursor.getString(cursor.getColumnIndex(UUID)); + template.lastUsed = cursor.getLong(cursor.getColumnIndex(LAST_USED)); + template.statusMessage = cursor.getString(cursor.getColumnIndex(MESSAGE)); + template.status = + Presence.Status.fromShowString(cursor.getString(cursor.getColumnIndex(STATUS))); + return template; + } + + public Presence.Status getStatus() { + return status; + } + + public String getStatusMessage() { + return statusMessage; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + PresenceTemplate template = (PresenceTemplate) o; + + if (!Objects.equals(statusMessage, template.statusMessage)) return false; + return status == template.status; + } + + @Override + public int hashCode() { + int result = statusMessage != null ? statusMessage.hashCode() : 0; + result = 31 * result + status.hashCode(); + return result; + } + + @Override + public String toString() { + return statusMessage; + } } diff --git a/src/main/java/eu/siacs/conversations/entities/ReadByMarker.java b/src/main/java/eu/siacs/conversations/entities/ReadByMarker.java index 4b1254f0761bee318809cd90f0d5ded8b5a71260..f8e0ace1a1f90c4a587349d1e4ebd2a72eb1bfe4 100644 --- a/src/main/java/eu/siacs/conversations/entities/ReadByMarker.java +++ b/src/main/java/eu/siacs/conversations/entities/ReadByMarker.java @@ -1,171 +1,167 @@ package eu.siacs.conversations.entities; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - +import eu.siacs.conversations.xmpp.Jid; import java.util.Collection; +import java.util.Objects; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; - -import eu.siacs.conversations.xmpp.Jid; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; public class ReadByMarker { - private ReadByMarker() { - - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - ReadByMarker marker = (ReadByMarker) o; - - if (fullJid != null ? !fullJid.equals(marker.fullJid) : marker.fullJid != null) - return false; - return realJid != null ? realJid.equals(marker.realJid) : marker.realJid == null; - - } - - @Override - public int hashCode() { - int result = fullJid != null ? fullJid.hashCode() : 0; - result = 31 * result + (realJid != null ? realJid.hashCode() : 0); - return result; - } - - private Jid fullJid; - private Jid realJid; - - public Jid getFullJid() { - return fullJid; - } - - public Jid getRealJid() { - return realJid; - } - - public JSONObject toJson() { - JSONObject jsonObject = new JSONObject(); - if (fullJid != null) { - try { - jsonObject.put("fullJid", fullJid.toString()); - } catch (JSONException e) { - //ignore - } - } - if (realJid != null) { - try { - jsonObject.put("realJid", realJid.toString()); - } catch (JSONException e) { - //ignore - } - } - return jsonObject; - } - - public static Set fromJson(final JSONArray jsonArray) { - final Set readByMarkers = new CopyOnWriteArraySet<>(); - for(int i = 0; i < jsonArray.length(); ++i) { - try { - readByMarkers.add(fromJson(jsonArray.getJSONObject(i))); - } catch (JSONException e) { - //ignored - } - } - return readByMarkers; - } - - public static ReadByMarker from(Jid fullJid, Jid realJid) { - final ReadByMarker marker = new ReadByMarker(); - marker.fullJid = fullJid; - marker.realJid = realJid == null ? null : realJid.asBareJid(); - return marker; - } - - public static ReadByMarker from(Message message) { - final ReadByMarker marker = new ReadByMarker(); - marker.fullJid = message.getCounterpart(); - marker.realJid = message.getTrueCounterpart(); - return marker; - } - - public static ReadByMarker from(MucOptions.User user) { - final ReadByMarker marker = new ReadByMarker(); - marker.fullJid = user.getFullJid(); - marker.realJid = user.getRealJid(); - return marker; - } - - public static Set from(Collection users) { - final Set markers = new CopyOnWriteArraySet<>(); - for(MucOptions.User user : users) { - markers.add(from(user)); - } - return markers; - } - - public static ReadByMarker fromJson(JSONObject jsonObject) { - ReadByMarker marker = new ReadByMarker(); - try { - marker.fullJid = Jid.of(jsonObject.getString("fullJid")); - } catch (JSONException | IllegalArgumentException e) { - marker.fullJid = null; - } - try { - marker.realJid = Jid.of(jsonObject.getString("realJid")); - } catch (JSONException | IllegalArgumentException e) { - marker.realJid = null; - } - return marker; - } - - public static Set fromJsonString(String json) { - try { - return fromJson(new JSONArray(json)); - } catch (final JSONException | NullPointerException e) { - return new CopyOnWriteArraySet<>(); - } - } - - public static JSONArray toJson(final Set readByMarkers) { - final JSONArray jsonArray = new JSONArray(); - for(final ReadByMarker marker : readByMarkers) { - jsonArray.put(marker.toJson()); - } - return jsonArray; - } - - public static boolean contains(ReadByMarker needle, final Set readByMarkers) { - for(final ReadByMarker marker : readByMarkers) { - if (marker.realJid != null && needle.realJid != null) { - if (marker.realJid.asBareJid().equals(needle.realJid.asBareJid())) { - return true; - } - } else if (marker.fullJid != null && needle.fullJid != null) { - if (marker.fullJid.equals(needle.fullJid)) { - return true; - } - } - } - return false; - } - - public static boolean allUsersRepresented(Collection users, Set markers) { - for(MucOptions.User user : users) { - if (!contains(from(user),markers)) { - return false; - } - } - return true; - } - - public static boolean allUsersRepresented(Collection users, Set markers, ReadByMarker marker) { - final Set markersCopy = new CopyOnWriteArraySet<>(markers); - markersCopy.add(marker); - return allUsersRepresented(users, markersCopy); - } - + private ReadByMarker() {} + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ReadByMarker marker = (ReadByMarker) o; + + if (!Objects.equals(fullJid, marker.fullJid)) return false; + return Objects.equals(realJid, marker.realJid); + } + + @Override + public int hashCode() { + int result = fullJid != null ? fullJid.hashCode() : 0; + result = 31 * result + (realJid != null ? realJid.hashCode() : 0); + return result; + } + + private Jid fullJid; + private Jid realJid; + + public Jid getFullJid() { + return fullJid; + } + + public Jid getRealJid() { + return realJid; + } + + public JSONObject toJson() { + JSONObject jsonObject = new JSONObject(); + if (fullJid != null) { + try { + jsonObject.put("fullJid", fullJid.toString()); + } catch (JSONException e) { + // ignore + } + } + if (realJid != null) { + try { + jsonObject.put("realJid", realJid.toString()); + } catch (JSONException e) { + // ignore + } + } + return jsonObject; + } + + public static Set fromJson(final JSONArray jsonArray) { + final Set readByMarkers = new CopyOnWriteArraySet<>(); + for (int i = 0; i < jsonArray.length(); ++i) { + try { + readByMarkers.add(fromJson(jsonArray.getJSONObject(i))); + } catch (JSONException e) { + // ignored + } + } + return readByMarkers; + } + + public static ReadByMarker from(Jid fullJid, Jid realJid) { + final ReadByMarker marker = new ReadByMarker(); + marker.fullJid = fullJid; + marker.realJid = realJid == null ? null : realJid.asBareJid(); + return marker; + } + + public static ReadByMarker from(Message message) { + final ReadByMarker marker = new ReadByMarker(); + marker.fullJid = message.getCounterpart(); + marker.realJid = message.getTrueCounterpart(); + return marker; + } + + public static ReadByMarker from(MucOptions.User user) { + final ReadByMarker marker = new ReadByMarker(); + marker.fullJid = user.getFullJid(); + marker.realJid = user.getRealJid(); + return marker; + } + + public static Set from(Collection users) { + final Set markers = new CopyOnWriteArraySet<>(); + for (MucOptions.User user : users) { + markers.add(from(user)); + } + return markers; + } + + public static ReadByMarker fromJson(JSONObject jsonObject) { + ReadByMarker marker = new ReadByMarker(); + try { + marker.fullJid = Jid.of(jsonObject.getString("fullJid")); + } catch (JSONException | IllegalArgumentException e) { + marker.fullJid = null; + } + try { + marker.realJid = Jid.of(jsonObject.getString("realJid")); + } catch (JSONException | IllegalArgumentException e) { + marker.realJid = null; + } + return marker; + } + + public static Set fromJsonString(String json) { + try { + return fromJson(new JSONArray(json)); + } catch (final JSONException | NullPointerException e) { + return new CopyOnWriteArraySet<>(); + } + } + + public static JSONArray toJson(final Set readByMarkers) { + final JSONArray jsonArray = new JSONArray(); + for (final ReadByMarker marker : readByMarkers) { + jsonArray.put(marker.toJson()); + } + return jsonArray; + } + + public static boolean contains(ReadByMarker needle, final Set readByMarkers) { + for (final ReadByMarker marker : readByMarkers) { + if (marker.realJid != null && needle.realJid != null) { + if (marker.realJid.asBareJid().equals(needle.realJid.asBareJid())) { + return true; + } + } else if (marker.fullJid != null && needle.fullJid != null) { + if (marker.fullJid.equals(needle.fullJid)) { + return true; + } + } + } + return false; + } + + public static boolean allUsersRepresented( + Collection users, Set markers) { + for (MucOptions.User user : users) { + if (!contains(from(user), markers)) { + return false; + } + } + return true; + } + + public static boolean allUsersRepresented( + Collection users, Set markers, ReadByMarker marker) { + final Set markersCopy = new CopyOnWriteArraySet<>(markers); + markersCopy.add(marker); + return allUsersRepresented(users, markersCopy); + } } diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index d648536699267bdfa7b68d10b719b1c777ef1cdb..2e95cfe69e88ac95d232dbe2c2308b1cd0833c7e 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -18,6 +18,22 @@ import org.whispersystems.libsignal.state.SignedPreKeyRecord; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.IOException; +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Bookmark; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.DownloadableFile; +import eu.siacs.conversations.services.MessageArchiveService; +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xml.Namespace; +import eu.siacs.conversations.xmpp.Jid; +import eu.siacs.conversations.xmpp.forms.Data; +import eu.siacs.conversations.xmpp.pep.Avatar; +import im.conversations.android.xmpp.model.stanza.Iq; +import im.conversations.android.xmpp.model.upload.Request; import java.nio.ByteBuffer; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; @@ -494,13 +510,13 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public Iq requestHttpUploadLegacySlot(Jid host, DownloadableFile file, String mime) { + public Iq requestHttpUploadSlot( + final Jid host, final DownloadableFile file, final String mime) { final Iq packet = new Iq(Iq.Type.GET); packet.setTo(host); - Element request = packet.addChild("request", Namespace.HTTP_UPLOAD_LEGACY); - request.addChild("filename").setContent(convertFilename(file.getName())); - request.addChild("size").setContent(String.valueOf(file.getExpectedSize())); - request.addChild("content-type").setContent(mime); + final var request = packet.addExtension(new Request()); + request.setFilename(convertFilename(file.getName())); + request.setSize(file.getExpectedSize()); return packet; } diff --git a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java index 33899242c1ff659db71d57972d278c222fefe859..86da4012f4d2c51d260d78c1ee95d597bd942b1c 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java @@ -127,10 +127,7 @@ public class HttpConnectionManager extends AbstractConnectionManager { } } HttpUploadConnection connection = - new HttpUploadConnection( - message, - Method.determine(message.getConversation().getAccount()), - this, cb); + new HttpUploadConnection(message, this, cb); connection.init(delay); this.uploadConnections.add(connection); } diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index f6df2639662c72203ca3de6457867dd1fadfc257..2ef2a3368c2f5dce4cbc0320e49db90688612295 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -3,20 +3,12 @@ package eu.siacs.conversations.http; import static eu.siacs.conversations.utils.Random.SECURE_RANDOM; import android.util.Log; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; - -import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.Future; - import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.DownloadableFile; @@ -25,6 +17,10 @@ import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.services.AbstractConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Future; import okhttp3.Call; import okhttp3.Callback; import okhttp3.OkHttpClient; @@ -32,17 +28,14 @@ import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; -public class HttpUploadConnection implements Transferable, AbstractConnectionManager.ProgressListener { +public class HttpUploadConnection + implements Transferable, AbstractConnectionManager.ProgressListener { - static final List WHITE_LISTED_HEADERS = Arrays.asList( - "Authorization", - "Cookie", - "Expires" - ); + static final List WHITE_LISTED_HEADERS = + Arrays.asList("Authorization", "Cookie", "Expires"); private final HttpConnectionManager mHttpConnectionManager; private final XmppConnectionService mXmppConnectionService; - private final Method method; private boolean delayed = false; private DownloadableFile file; private final Message message; @@ -54,9 +47,8 @@ public class HttpUploadConnection implements Transferable, AbstractConnectionMan private ListenableFuture slotFuture; private Runnable cb; - public HttpUploadConnection(Message message, Method method, HttpConnectionManager httpConnectionManager, Runnable cb) { + public HttpUploadConnection(final Message message, final HttpConnectionManager httpConnectionManager, final Runnable cb) { this.message = message; - this.method = method; this.mHttpConnectionManager = httpConnectionManager; this.mXmppConnectionService = httpConnectionManager.getXmppConnectionService(); this.cb = cb; @@ -90,13 +82,13 @@ public class HttpUploadConnection implements Transferable, AbstractConnectionMan final ListenableFuture slotFuture = this.slotFuture; if (slotFuture != null && !slotFuture.isDone()) { if (slotFuture.cancel(true)) { - Log.d(Config.LOGTAG,"cancelled slot requester"); + Log.d(Config.LOGTAG, "cancelled slot requester"); } } final Call call = this.mostRecentCall; if (call != null && !call.isCanceled()) { call.cancel(); - Log.d(Config.LOGTAG,"cancelled HTTP request"); + Log.d(Config.LOGTAG, "cancelled HTTP request"); } } @@ -104,8 +96,13 @@ public class HttpUploadConnection implements Transferable, AbstractConnectionMan finish(); final Call call = this.mostRecentCall; final Future slotFuture = this.slotFuture; - final boolean cancelled = (call != null && call.isCanceled()) || (slotFuture != null && slotFuture.isCancelled()); - mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED, cancelled ? Message.ERROR_MESSAGE_CANCELLED : errorMessage); + final boolean cancelled = + (call != null && call.isCanceled()) + || (slotFuture != null && slotFuture.isCancelled()); + mXmppConnectionService.markMessage( + message, + Message.STATUS_SEND_FAILED, + cancelled ? Message.ERROR_MESSAGE_CANCELLED : errorMessage); if (cb != null) cb.run(); } @@ -118,7 +115,8 @@ public class HttpUploadConnection implements Transferable, AbstractConnectionMan final Account account = message.getConversation().getAccount(); this.file = mXmppConnectionService.getFileBackend().getFile(message, false); final String mime; - if (message.getEncryption() == Message.ENCRYPTION_PGP || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { + if (message.getEncryption() == Message.ENCRYPTION_PGP + || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { mime = "application/pgp-encrypted"; } else { mime = this.file.getMimeType(); @@ -132,75 +130,86 @@ public class HttpUploadConnection implements Transferable, AbstractConnectionMan this.file.setKeyAndIv(this.key); } this.file.setExpectedSize(originalFileSize + (file.getKey() != null ? 16 : 0)); - this.slotFuture = new SlotRequester(mXmppConnectionService).request(method, account, file, message.getFileParams().getName(), mime); - Futures.addCallback(this.slotFuture, new FutureCallback() { - @Override - public void onSuccess(@Nullable SlotRequester.Slot result) { - HttpUploadConnection.this.slot = result; - try { - HttpUploadConnection.this.upload(); - } catch (final Exception e) { - fail(e.getMessage()); - } - } + message.resetFileParams(); + this.slotFuture = new SlotRequester(mXmppConnectionService).request(account, file, mime); + Futures.addCallback( + this.slotFuture, + new FutureCallback<>() { + @Override + public void onSuccess(@Nullable SlotRequester.Slot result) { + HttpUploadConnection.this.slot = result; + try { + HttpUploadConnection.this.upload(); + } catch (final Exception e) { + fail(e.getMessage()); + } + } - @Override - public void onFailure(@NonNull final Throwable throwable) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to request slot", throwable); - // TODO consider fall back to jingle in 1-on-1 chats with exactly one online presence - fail(throwable.getMessage()); - } - }, MoreExecutors.directExecutor()); + @Override + public void onFailure(@NonNull final Throwable throwable) { + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + ": unable to request slot", + throwable); + // TODO consider fall back to jingle in 1-on-1 chats with exactly one online + // presence + fail(throwable.getMessage()); + } + }, + MoreExecutors.directExecutor()); message.setTransferable(this); mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND); } private void upload() { - final OkHttpClient client = mHttpConnectionManager.buildHttpClient( - slot.put, - message.getConversation().getAccount(), - 0, - true - ); + final OkHttpClient client = + mHttpConnectionManager.buildHttpClient( + slot.put, message.getConversation().getAccount(), 0, true); final RequestBody requestBody = AbstractConnectionManager.requestBody(file, this); - final Request request = new Request.Builder() - .url(slot.put) - .put(requestBody) - .headers(slot.headers) - .build(); + final Request request = + new Request.Builder().url(slot.put).put(requestBody).headers(slot.headers).build(); Log.d(Config.LOGTAG, "uploading file to " + slot.put); this.mostRecentCall = client.newCall(request); - this.mostRecentCall.enqueue(new Callback() { - @Override - public void onFailure(@NonNull Call call, IOException e) { - Log.d(Config.LOGTAG, "http upload failed", e); - fail(e.getMessage()); - } - - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - final int code = response.code(); - if (code == 200 || code == 201) { - Log.d(Config.LOGTAG, "finished uploading file"); - final String get; - if (key != null) { - get = AesGcmURL.toAesGcmUrl(slot.get.newBuilder().fragment(CryptoHelper.bytesToHex(key)).build()); - } else { - get = slot.get.toString(); + this.mostRecentCall.enqueue( + new Callback() { + @Override + public void onFailure(@NonNull Call call, IOException e) { + Log.d(Config.LOGTAG, "http upload failed", e); + fail(e.getMessage()); } - mXmppConnectionService.getFileBackend().updateFileParams(message, get); - mXmppConnectionService.getFileBackend().updateMediaScanner(file); - finish(); - if (!message.isPrivateMessage()) { - message.setCounterpart(message.getConversation().getJid().asBareJid()); + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + final int code = response.code(); + if (code == 200 || code == 201) { + Log.d(Config.LOGTAG, "finished uploading file"); + final String get; + if (key != null) { + get = + AesGcmURL.toAesGcmUrl( + slot.get + .newBuilder() + .fragment(CryptoHelper.bytesToHex(key)) + .build()); + } else { + get = slot.get.toString(); + } + mXmppConnectionService.getFileBackend().updateFileParams(message, get); + mXmppConnectionService.getFileBackend().updateMediaScanner(file); + finish(); + if (!message.isPrivateMessage()) { + message.setCounterpart( + message.getConversation().getJid().asBareJid()); + } + mXmppConnectionService.resendMessage(message, delayed, cb); + } else { + Log.d( + Config.LOGTAG, + "http upload failed because response code was " + code); + fail("http upload failed because response code was " + code); + } } - mXmppConnectionService.resendMessage(message, delayed, cb); - } else { - Log.d(Config.LOGTAG, "http upload failed because response code was " + code); - fail("http upload failed because response code was " + code); - } - } - }); + }); } public Message getMessage() { diff --git a/src/main/java/eu/siacs/conversations/http/Method.java b/src/main/java/eu/siacs/conversations/http/Method.java deleted file mode 100644 index 47dae2b30cea010df7ba1f6a5c4f004c21fcf96d..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/http/Method.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2018, Daniel Gultsch All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors - * may be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package eu.siacs.conversations.http; - -import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.xmpp.XmppConnection; - -public enum Method { - HTTP_UPLOAD, HTTP_UPLOAD_LEGACY; - - public static Method determine(Account account) { - XmppConnection.Features features = account.getXmppConnection() == null ? null : account.getXmppConnection().getFeatures(); - if (features == null) { - return HTTP_UPLOAD; - } - if (features.useLegacyHttpUpload()) { - return HTTP_UPLOAD_LEGACY; - } else if (features.httpUpload(0)) { - return HTTP_UPLOAD; - } else { - return HTTP_UPLOAD; - } - } -} diff --git a/src/main/java/eu/siacs/conversations/http/SlotRequester.java b/src/main/java/eu/siacs/conversations/http/SlotRequester.java index b3af132b7e44b148257d8c5d68217bbb24ea2820..5bb5d7772b253857e7cc8eab81c60c4aedb9d1c2 100644 --- a/src/main/java/eu/siacs/conversations/http/SlotRequester.java +++ b/src/main/java/eu/siacs/conversations/http/SlotRequester.java @@ -29,21 +29,22 @@ package eu.siacs.conversations.http; +import android.util.Log; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.SettableFuture; - -import java.util.Map; - +import com.google.common.util.concurrent.MoreExecutors; +import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.DownloadableFile; -import eu.siacs.conversations.parser.IqParser; import eu.siacs.conversations.services.XmppConnectionService; -import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.IqResponseException; import eu.siacs.conversations.xmpp.Jid; import im.conversations.android.xmpp.model.stanza.Iq; +import im.conversations.android.xmpp.model.upload.Header; +import im.conversations.android.xmpp.model.upload.Slot; +import java.util.Map; import okhttp3.Headers; import okhttp3.HttpUrl; @@ -55,83 +56,54 @@ public class SlotRequester { this.service = service; } - public ListenableFuture request(Method method, Account account, DownloadableFile file, String name, String mime) { - if (method == Method.HTTP_UPLOAD_LEGACY) { - final Jid host = account.getXmppConnection().findDiscoItemByFeature(Namespace.HTTP_UPLOAD_LEGACY); - return requestHttpUploadLegacy(account, host, file, mime); - } else { - final Jid host = account.getXmppConnection().findDiscoItemByFeature(Namespace.HTTP_UPLOAD); - return requestHttpUpload(account, host, file, name, mime); + public ListenableFuture request( + final Account account, final DownloadableFile file, final String mime) { + final var result = + account.getXmppConnection() + .getServiceDiscoveryResultByFeature(Namespace.HTTP_UPLOAD); + if (result == null) { + return Futures.immediateFailedFuture( + new IllegalStateException("No HTTP upload host found")); } + return requestHttpUpload(account, result.getKey(), file, mime); } - private ListenableFuture requestHttpUploadLegacy(Account account, Jid host, DownloadableFile file, String mime) { - final SettableFuture future = SettableFuture.create(); - final Iq request = service.getIqGenerator().requestHttpUploadLegacySlot(host, file, mime); - service.sendIqPacket(account, request, (packet) -> { - if (packet.getType() == Iq.Type.RESULT) { - final Element slotElement = packet.findChild("slot", Namespace.HTTP_UPLOAD_LEGACY); - if (slotElement != null) { - try { - final String putUrl = slotElement.findChildContent("put"); - final String getUrl = slotElement.findChildContent("get"); - if (getUrl != null && putUrl != null) { - final Slot slot = new Slot( - HttpUrl.get(putUrl), - HttpUrl.get(getUrl), - Headers.of("Content-Type", mime == null ? "application/octet-stream" : mime) - ); - future.set(slot); - return; - } - } catch (final IllegalArgumentException e) { - future.setException(e); - return; + private ListenableFuture requestHttpUpload( + final Account account, final Jid host, final DownloadableFile file, final String mime) { + final Iq request = service.getIqGenerator().requestHttpUploadSlot(host, file, mime); + final var iqFuture = service.sendIqPacket(account, request); + return Futures.transform( + iqFuture, + response -> { + final var slot = + response.getExtension( + im.conversations.android.xmpp.model.upload.Slot.class); + if (slot == null) { + Log.d(Config.LOGTAG, "-->" + response); + throw new IllegalStateException("Slot not found in IQ response"); } - } - } - future.setException(new IqResponseException(IqParser.extractErrorMessage(packet))); - }); - return future; - } - - private ListenableFuture requestHttpUpload(Account account, Jid host, DownloadableFile file, String fname, String mime) { - final SettableFuture future = SettableFuture.create(); - final Iq request = service.getIqGenerator().requestHttpUploadSlot(host, file, fname, mime); - service.sendIqPacket(account, request, (packet) -> { - if (packet.getType() == Iq.Type.RESULT) { - final Element slotElement = packet.findChild("slot", Namespace.HTTP_UPLOAD); - if (slotElement != null) { - try { - final Element put = slotElement.findChild("put"); - final Element get = slotElement.findChild("get"); - final String putUrl = put == null ? null : put.getAttribute("url"); - final String getUrl = get == null ? null : get.getAttribute("url"); - if (getUrl != null && putUrl != null) { - final ImmutableMap.Builder headers = new ImmutableMap.Builder<>(); - for (final Element child : put.getChildren()) { - if ("header".equals(child.getName())) { - final String name = child.getAttribute("name"); - final String value = child.getContent(); - if (HttpUploadConnection.WHITE_LISTED_HEADERS.contains(name) && value != null && !value.trim().contains("\n")) { - headers.put(name, value.trim()); - } - } - } - headers.put("Content-Type", mime == null ? "application/octet-stream" : mime); - final Slot slot = new Slot(HttpUrl.get(putUrl), HttpUrl.get(getUrl), headers.build()); - future.set(slot); - return; + final var getUrl = slot.getGetUrl(); + final var put = slot.getPut(); + if (getUrl == null || put == null) { + throw new IllegalStateException("Missing get or put in slot response"); + } + final var putUrl = put.getUrl(); + if (putUrl == null) { + throw new IllegalStateException("Missing put url"); + } + final var headers = new ImmutableMap.Builder(); + for (final Header header : put.getHeaders()) { + final String name = header.getHeaderName(); + final String value = header.getContent(); + if (Strings.isNullOrEmpty(value) || value.contains("\n")) { + continue; } - } catch (final IllegalArgumentException e) { - future.setException(e); - return; + headers.put(name, value.trim()); } - } - } - future.setException(new IqResponseException(IqParser.extractErrorMessage(packet))); - }); - return future; + headers.put("Content-Type", mime == null ? "application/octet-stream" : mime); + return new Slot(putUrl, getUrl, headers.buildKeepingLast()); + }, + MoreExecutors.directExecutor()); } public static class Slot { diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java index c562da2ebc601ffa1a9c2dac393bb58c1846263a..eaf9ad6c583e7daeb0a3d1a33cceb864d76ccb62 100644 --- a/src/main/java/eu/siacs/conversations/parser/IqParser.java +++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java @@ -204,7 +204,7 @@ public class IqParser extends AbstractParser implements Consumer { + "Encountered invalid node in PEP (" + e.getMessage() + "):" - + device.toString() + + device + ", skipping..."); } } @@ -328,7 +328,7 @@ public class IqParser extends AbstractParser implements Consumer { AxolotlService.LOGPREFIX + " : " + "could not parse preKeyId from preKey " - + preKeyPublicElement.toString()); + + preKeyPublicElement); } catch (Throwable e) { Log.e( Config.LOGTAG, diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 3b6ba66545da06bdfb17edac16b9c8af7a402894..8be9c02c10e556e9dddc7797950d8db4b1093857 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -361,7 +361,6 @@ public class MessageParser extends AbstractParser } } else if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) { final var retractions = items.getRetractions(); - ; for (final var item : items.getItemMap(Conference.class).entrySet()) { final Bookmark bookmark = Bookmark.parseFromItem(item.getKey(), item.getValue(), account); diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 53d1e25730f643c765a60db2ff04190ca1e3cf59..d4736e5888d3a273a54f8dc11f2979e1ca883ef4 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -390,7 +390,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { final SQLiteDatabase db = getWritableDatabase(); final Stopwatch stopwatch = Stopwatch.createStarted(); db.execSQL(COPY_PREEXISTING_ENTRIES); - Log.d(Config.LOGTAG, "rebuilt message index in " + stopwatch.stop().toString()); + Log.d(Config.LOGTAG, "rebuilt message index in " + stopwatch.stop()); } public static synchronized DatabaseBackend getInstance(Context context) { @@ -1978,7 +1978,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { + " is not null and conversationUuid=(select uuid from conversations where" + " accountUuid=? and (contactJid=? or contactJid like ?)) order by" + " timeSent desc"; - final String[] args = {account, jid.toString(), jid.toString() + "/%"}; + final String[] args = {account, jid.toString(), jid + "/%"}; Cursor cursor = db.rawQuery(SQL + (limit > 0 ? " limit " + limit : ""), args); List filesPaths = new ArrayList<>(); while (cursor.moveToNext()) { diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index fddaf86e3cb88c4d365fcc65237665150829981e..2a681837fc09f318686c2ac5cb9243265073262a 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -1722,7 +1722,7 @@ public class FileBackend { Log.d(Config.LOGTAG, "settled on char length " + chars + " with quality=" + quality); final Avatar avatar = new Avatar(); avatar.sha1sum = CryptoHelper.bytesToHex(digest.digest()); - avatar.image = new String(mByteArrayOutputStream.toByteArray()); + avatar.image = mByteArrayOutputStream.toString(); if (format.equals(Bitmap.CompressFormat.WEBP)) { avatar.type = "image/webp"; } else if (format.equals(Bitmap.CompressFormat.JPEG)) { @@ -1767,7 +1767,7 @@ public class FileBackend { os.flush(); os.close(); avatar.sha1sum = CryptoHelper.bytesToHex(digest.digest()); - avatar.image = new String(mByteArrayOutputStream.toByteArray()); + avatar.image = mByteArrayOutputStream.toString(); avatar.height = options.outHeight; avatar.width = options.outWidth; avatar.type = options.outMimeType; diff --git a/src/main/java/eu/siacs/conversations/services/CallIntegration.java b/src/main/java/eu/siacs/conversations/services/CallIntegration.java index 01534fb08ce3d9b78401d0673af555def1182629..19e6175b6673b4f4990359484a7d8b5a737e7572 100644 --- a/src/main/java/eu/siacs/conversations/services/CallIntegration.java +++ b/src/main/java/eu/siacs/conversations/services/CallIntegration.java @@ -545,10 +545,7 @@ public class CallIntegration extends Connection { return false; } // SailfishOS's AppSupport do not support Call Integration - if (Build.MODEL.endsWith("(AppSupport)")) { - return false; - } - return true; + return !Build.MODEL.endsWith("(AppSupport)"); } public static boolean notSelfManaged(final Context context) { diff --git a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java index b03670feb592121acb9b7312e6040be78fa3a444..dcf388cea3d72656801440995d0508bd34a96c33 100644 --- a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java +++ b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java @@ -3,15 +3,7 @@ package eu.siacs.conversations.services; import static eu.siacs.conversations.utils.Random.SECURE_RANDOM; import android.util.Log; - import androidx.annotation.NonNull; - -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; @@ -25,6 +17,11 @@ import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; import eu.siacs.conversations.xmpp.mam.MamReference; import im.conversations.android.xmpp.model.stanza.Iq; import im.conversations.android.xmpp.model.stanza.Message; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { @@ -90,7 +87,6 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } return null; } - } MessageArchiveService(final XmppConnectionService service) { @@ -106,10 +102,13 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } } } - MamReference mamReference = MamReference.max( - mXmppConnectionService.databaseBackend.getLastMessageReceived(account), - mXmppConnectionService.databaseBackend.getLastClearDate(account) - ); + MamReference mamReference = + MamReference.max( + mXmppConnectionService.databaseBackend.getLastMessageReceived(account), + mXmppConnectionService.databaseBackend.getLastClearDate(account)); + mamReference = + MamReference.max( + mamReference, mXmppConnectionService.getAutomaticMessageDeletionDate()); long endCatchup = account.getXmppConnection().getLastSessionEstablished(); final Query query; if (mamReference.getTimestamp() == 0) { @@ -118,7 +117,9 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { long startCatchup = endCatchup - Config.MAM_MAX_CATCHUP; List conversations = mXmppConnectionService.getConversations(); for (Conversation conversation : conversations) { - if (conversation.getMode() == Conversation.MODE_SINGLE && conversation.getAccount() == account && startCatchup > conversation.getLastMessageTransmitted().getTimestamp()) { + if (conversation.getMode() == Conversation.MODE_SINGLE + && conversation.getAccount() == account + && startCatchup > conversation.getLastMessageTransmitted().getTimestamp()) { this.query(conversation, startCatchup, true); } } @@ -133,27 +134,21 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } void catchupMUC(final Conversation conversation) { - if (conversation.getLastMessageTransmitted().getTimestamp() < 0 && conversation.countMessages() == 0) { - query(conversation, - new MamReference(0), - 0, - true); + if (conversation.getLastMessageTransmitted().getTimestamp() < 0 + && conversation.countMessages() == 0) { + query(conversation, new MamReference(0), 0, true); } else { - query(conversation, - conversation.getLastMessageTransmitted(), - 0, - true); + query(conversation, conversation.getLastMessageTransmitted(), 0, true); } } public Query query(final Conversation conversation) { - if (conversation.getLastMessageTransmitted().getTimestamp() < 0 && conversation.countMessages() == 0) { - return query(conversation, - new MamReference(0), - System.currentTimeMillis(), - false); + if (conversation.getLastMessageTransmitted().getTimestamp() < 0 + && conversation.countMessages() == 0) { + return query(conversation, new MamReference(0), System.currentTimeMillis(), false); } else { - return query(conversation, + return query( + conversation, conversation.getLastMessageTransmitted(), conversation.getAccount().getXmppConnection().getLastSessionEstablished(), false); @@ -167,7 +162,11 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } else { synchronized (this.queries) { for (Query query : this.queries) { - if (query.getAccount() == account && query.isCatchup() && ((conversation.getMode() == Conversation.MODE_SINGLE && query.getWith() == null) || query.getConversation() == conversation)) { + if (query.getAccount() == account + && query.isCatchup() + && ((conversation.getMode() == Conversation.MODE_SINGLE + && query.getWith() == null) + || query.getConversation() == conversation)) { return true; } } @@ -177,10 +176,12 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } public Query query(final Conversation conversation, long end, boolean allowCatchup) { - return this.query(conversation, conversation.getLastMessageTransmitted(), end, allowCatchup); + return this.query( + conversation, conversation.getLastMessageTransmitted(), end, allowCatchup); } - public Query query(Conversation conversation, MamReference start, long end, boolean allowCatchup) { + public Query query( + Conversation conversation, MamReference start, long end, boolean allowCatchup) { synchronized (this.queries) { final Query query; final MamReference startActual = start; @@ -189,9 +190,17 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { query.reference = conversation.getFirstMamReference(); } else { if (allowCatchup) { - MamReference maxCatchup = MamReference.max(startActual, System.currentTimeMillis() - Config.MAM_MAX_CATCHUP); + MamReference maxCatchup = + MamReference.max( + startActual, + System.currentTimeMillis() - Config.MAM_MAX_CATCHUP); if (maxCatchup.greaterThan(startActual)) { - Query reverseCatchup = new Query(conversation, startActual, maxCatchup.getTimestamp(), false); + Query reverseCatchup = + new Query( + conversation, + startActual, + maxCatchup.getTimestamp(), + false); this.queries.add(reverseCatchup); this.execute(reverseCatchup); } @@ -230,40 +239,57 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { if (account.getStatus() == Account.State.ONLINE) { final Conversation conversation = query.getConversation(); if (conversation != null && conversation.getStatus() == Conversation.STATUS_ARCHIVED) { - throw new IllegalStateException("Attempted to run MAM query for archived conversation"); - } - Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": running mam query " + query.toString()); - final Iq packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query); - this.mXmppConnectionService.sendIqPacket(account, packet, (p) -> { - final Element fin = p.findChild("fin", query.version.namespace); - if (p.getType() == Iq.Type.TIMEOUT) { - synchronized (this.queries) { - this.queries.remove(query); - if (query.hasCallback()) { - query.callback(false); + throw new IllegalStateException( + "Attempted to run MAM query for archived conversation"); + } + Log.d( + Config.LOGTAG, + account.getJid().asBareJid().toString() + ": running mam query " + query); + final Iq packet = + this.mXmppConnectionService + .getIqGenerator() + .queryMessageArchiveManagement(query); + this.mXmppConnectionService.sendIqPacket( + account, + packet, + (p) -> { + final Element fin = p.findChild("fin", query.version.namespace); + if (p.getType() == Iq.Type.TIMEOUT) { + synchronized (this.queries) { + this.queries.remove(query); + if (query.hasCallback()) { + query.callback(false); + } + } + } else if (p.getType() == Iq.Type.RESULT && fin != null) { + final boolean running; + synchronized (this.queries) { + running = this.queries.contains(query); + } + if (running) { + processFin(query, fin); + } else { + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + + ": ignoring MAM iq result because query had been" + + " killed"); + } + } else if (p.getType() == Iq.Type.RESULT && query.isLegacy()) { + // do nothing + } else { + Log.d( + Config.LOGTAG, + account.getJid().asBareJid().toString() + + ": error executing mam: " + + p); + try { + finalizeQuery(query, true); + } catch (final IllegalStateException e) { + // ignored + } } - } - } else if (p.getType() == Iq.Type.RESULT && fin != null) { - final boolean running; - synchronized (this.queries) { - running = this.queries.contains(query); - } - if (running) { - processFin(query, fin); - } else { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring MAM iq result because query had been killed"); - } - } else if (p.getType() == Iq.Type.RESULT && query.isLegacy()) { - //do nothing - } else { - Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": error executing mam: " + p.toString()); - try { - finalizeQuery(query, true); - } catch (final IllegalStateException e) { - //ignored - } - } - }); + }); } else { synchronized (this.pendingQueries) { this.pendingQueries.add(query); @@ -319,7 +345,8 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { for (Query query : queries) { if (query.account == conversation.getAccount() && query.isCatchup()) { final Jid with = query.getWith() == null ? null : query.getWith().asBareJid(); - if ((conversation.getMode() == Conversational.MODE_SINGLE && with == null) || (conversation.getJid().asBareJid().equals(with))) { + if ((conversation.getMode() == Conversational.MODE_SINGLE && with == null) + || (conversation.getJid().asBareJid().equals(with))) { return true; } } @@ -328,7 +355,8 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { return false; } - boolean queryInProgress(Conversation conversation, XmppConnectionService.OnMoreMessagesLoaded callback) { + boolean queryInProgress( + Conversation conversation, XmppConnectionService.OnMoreMessagesLoaded callback) { synchronized (this.queries) { for (Query query : queries) { if (query.conversation == conversation) { @@ -360,12 +388,15 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { String count = set == null ? null : set.findChildContent("count"); Element first = set == null ? null : set.findChild("first"); Element relevant = query.getPagingOrder() == PagingOrder.NORMAL ? last : first; - boolean abort = (!query.isCatchup() && query.getTotalCount() >= Config.PAGE_SIZE) || query.getTotalCount() >= Config.MAM_MAX_MESSAGES; + boolean abort = + (!query.isCatchup() && query.getTotalCount() >= Config.PAGE_SIZE) + || query.getTotalCount() >= Config.MAM_MAX_MESSAGES; if (query.getConversation() != null) { query.getConversation().setFirstMamReference(first == null ? null : first.getContent()); } if (complete || relevant == null || abort) { - //TODO: FIX done logic to look at complete. using count is probably unreliable because it can be ommited and doesn’t work with paging. + // TODO: FIX done logic to look at complete. using count is probably unreliable because + // it can be ommited and doesn’t work with paging. boolean done; if (query.isCatchup()) { done = false; @@ -383,9 +414,21 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { done = done || (query.getActualMessageCount() == 0 && !query.isCatchup()); this.finalizeQuery(query, done); - Log.d(Config.LOGTAG, query.getAccount().getJid().asBareJid() + ": finished mam after " + query.getTotalCount() + "(" + query.getActualMessageCount() + ") messages. messages left=" + !done + " count=" + count); + Log.d( + Config.LOGTAG, + query.getAccount().getJid().asBareJid() + + ": finished mam after " + + query.getTotalCount() + + "(" + + query.getActualMessageCount() + + ") messages. messages left=" + + !done + + " count=" + + count); if (query.isCatchup() && query.getActualMessageCount() > 0) { - mXmppConnectionService.getNotificationService().finishBacklog(true, query.getAccount()); + mXmppConnectionService + .getNotificationService() + .finishBacklog(true, query.getAccount()); } if (query.isCatchup() && query.getPagingOrder() == PagingOrder.NORMAL && !complete && query.getConversation() != null) { // Going forward we stopped without completing due to limits @@ -415,11 +458,15 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { void kill(final Conversation conversation) { final ArrayList toBeKilled = new ArrayList<>(); synchronized (this.pendingQueries) { - for (final Iterator iterator = this.pendingQueries.iterator(); iterator.hasNext(); ) { + for (final Iterator iterator = this.pendingQueries.iterator(); + iterator.hasNext(); ) { final Query query = iterator.next(); if (query.getConversation() == conversation) { iterator.remove(); - Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": killed pending MAM query for archived conversation"); + Log.d( + Config.LOGTAG, + conversation.getAccount().getJid().asBareJid() + + ": killed pending MAM query for archived conversation"); } } } @@ -436,7 +483,9 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } private void kill(Query query) { - Log.d(Config.LOGTAG, query.getAccount().getJid().asBareJid() + ": killing mam query prematurely"); + Log.d( + Config.LOGTAG, + query.getAccount().getJid().asBareJid() + ": killing mam query prematurely"); query.callback = null; this.finalizeQuery(query, false); if (query.isCatchup() && query.getActualMessageCount() > 0) { @@ -448,11 +497,20 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { private void processPostponed(Query query) { query.account.getAxolotlService().processPostponed(); query.pendingReceiptRequests.removeAll(query.receiptRequests); - Log.d(Config.LOGTAG, query.getAccount().getJid().asBareJid() + ": found " + query.pendingReceiptRequests.size() + " pending receipt requests"); + Log.d( + Config.LOGTAG, + query.getAccount().getJid().asBareJid() + + ": found " + + query.pendingReceiptRequests.size() + + " pending receipt requests"); Iterator iterator = query.pendingReceiptRequests.iterator(); while (iterator.hasNext()) { ReceiptRequest rr = iterator.next(); - mXmppConnectionService.sendMessagePacket(query.account, mXmppConnectionService.getMessageGenerator().received(query.account, rr.getJid(), rr.getId())); + mXmppConnectionService.sendMessagePacket( + query.account, + mXmppConnectionService + .getMessageGenerator() + .received(query.account, rr.getJid(), rr.getId())); iterator.remove(); } } @@ -473,7 +531,8 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { @Override public void onAdvancedStreamFeaturesAvailable(Account account) { - if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().mam()) { + if (account.getXmppConnection() != null + && account.getXmppConnection().getFeatures().mam()) { this.catchup(account); } } @@ -500,14 +559,17 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { private boolean catchup = true; public final Version version; - Query(Conversation conversation, MamReference start, long end, boolean catchup, PagingOrder order) { this(conversation, start, end, catchup); this.pagingOrder = order; } Query(Conversation conversation, MamReference start, long end, boolean catchup) { - this(conversation.getAccount(), Version.get(conversation.getAccount(), conversation), catchup ? start : start.timeOnly(), end); + this( + conversation.getAccount(), + Version.get(conversation.getAccount(), conversation), + catchup ? start : start.timeOnly(), + end); this.conversation = conversation; this.pagingOrder = catchup ? PagingOrder.NORMAL : PagingOrder.REVERSE; this.catchup = catchup; @@ -530,7 +592,12 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } private Query page(String reference) { - Query query = new Query(this.account, this.version, new MamReference(this.start, reference), this.end); + Query query = + new Query( + this.account, + this.version, + new MamReference(this.start, reference), + this.end); query.conversation = conversation; query.totalCount = totalCount; query.actualCount = actualCount; diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index db94d0d42377377a99303a5b0b939d0577d58e48..add704c44e8e9b737d5713918894751c3f83630f 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -117,6 +117,7 @@ import io.ipfs.cid.Cid; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import eu.siacs.conversations.AppSettings; import eu.siacs.conversations.Config; @@ -234,6 +235,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; @@ -583,7 +585,7 @@ public class XmppConnectionService extends Service { private LruCache mDrawableCache; private final BroadcastReceiver mInternalEventReceiver = new InternalEventReceiver(); private final BroadcastReceiver mInternalRestrictedEventReceiver = - new RestrictedEventReceiver(Arrays.asList(TorServiceUtils.ACTION_STATUS)); + new RestrictedEventReceiver(List.of(TorServiceUtils.ACTION_STATUS)); private final BroadcastReceiver mInternalScreenEventReceiver = new InternalEventReceiver(); private EmojiSearch emojiSearch = null; @@ -5136,7 +5138,7 @@ public class XmppConnectionService extends Service { if (packet.getType() == Iq.Type.RESULT) { callback.onPushSucceeded(); } else { - Log.d(Config.LOGTAG, "failed: " + packet.toString()); + Log.d(Config.LOGTAG, "failed: " + packet); callback.onPushFailed(); } } @@ -5761,7 +5763,7 @@ public class XmppConnectionService extends Service { if (error == null) { Log.d(Config.LOGTAG, ERROR + "(server error)"); } else { - Log.d(Config.LOGTAG, ERROR + error.toString()); + Log.d(Config.LOGTAG, ERROR + error); } } if (callback != null) { @@ -6671,6 +6673,14 @@ public class XmppConnectionService extends Service { connection.sendCreateAccountWithCaptchaPacket(id, data); } + public ListenableFuture sendIqPacket(final Account account, final Iq request) { + final XmppConnection connection = account.getXmppConnection(); + if (connection == null) { + return Futures.immediateFailedFuture(new TimeoutException()); + } + return connection.sendIqPacket(request); + } + public void sendIqPacket(final Account account, final Iq packet, final Consumer callback) { sendIqPacket(account, packet, callback, null); } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 994dd65c7863ff91c086a79d0effa161fe63963a..851068d14fdab6aca46975cb830e582ed254a55f 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -604,8 +604,7 @@ public class ConversationFragment extends XmppFragment @Override public void onClick(View v) { Object tag = v.getTag(); - if (tag instanceof SendButtonAction) { - SendButtonAction action = (SendButtonAction) tag; + if (tag instanceof SendButtonAction action) { switch (action) { case TAKE_PHOTO: case RECORD_VIDEO: diff --git a/src/main/java/eu/siacs/conversations/ui/CreatePrivateGroupChatDialog.java b/src/main/java/eu/siacs/conversations/ui/CreatePrivateGroupChatDialog.java index ae3406a2d821b17fd6782bd236120b8a09dca636..88c378f9ba370a7ae03f59d6a7aeb6b43c31eba1 100644 --- a/src/main/java/eu/siacs/conversations/ui/CreatePrivateGroupChatDialog.java +++ b/src/main/java/eu/siacs/conversations/ui/CreatePrivateGroupChatDialog.java @@ -4,20 +4,15 @@ import android.app.Dialog; import android.content.Context; import android.os.Bundle; import android.widget.AutoCompleteTextView; - import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; import androidx.databinding.DataBindingUtil; import androidx.fragment.app.DialogFragment; - import com.google.android.material.dialog.MaterialAlertDialogBuilder; - -import java.util.ArrayList; -import java.util.List; - import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.DialogCreateConferenceBinding; import eu.siacs.conversations.ui.util.DelayedHintHelper; +import java.util.ArrayList; +import java.util.List; public class CreatePrivateGroupChatDialog extends DialogFragment { @@ -41,23 +36,36 @@ public class CreatePrivateGroupChatDialog extends DialogFragment { @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity()); + final MaterialAlertDialogBuilder builder = + new MaterialAlertDialogBuilder(requireActivity()); builder.setTitle(R.string.create_private_group_chat); - final DialogCreateConferenceBinding binding = DataBindingUtil.inflate(getActivity().getLayoutInflater(), R.layout.dialog_create_conference, null, false); + final DialogCreateConferenceBinding binding = + DataBindingUtil.inflate( + getActivity().getLayoutInflater(), + R.layout.dialog_create_conference, + null, + false); ArrayList mActivatedAccounts = getArguments().getStringArrayList(ACCOUNTS_LIST_KEY); - StartConversationActivity.populateAccountSpinner(getActivity(), mActivatedAccounts, binding.account); + StartConversationActivity.populateAccountSpinner( + getActivity(), mActivatedAccounts, binding.account); builder.setView(binding.getRoot()); - builder.setPositiveButton(R.string.choose_participants, (dialog, which) -> mListener.onCreateDialogPositiveClick(binding.account, binding.groupChatName.getText().toString().trim())); + builder.setPositiveButton( + R.string.choose_participants, + (dialog, which) -> + mListener.onCreateDialogPositiveClick( + binding.account, + binding.groupChatName.getText().toString().trim())); builder.setNegativeButton(R.string.cancel, null); DelayedHintHelper.setHint(R.string.providing_a_name_is_optional, binding.groupChatName); - binding.groupChatName.setOnEditorActionListener((v, actionId, event) -> { - mListener.onCreateDialogPositiveClick(binding.account, binding.groupChatName.getText().toString().trim()); - return true; - }); + binding.groupChatName.setOnEditorActionListener( + (v, actionId, event) -> { + mListener.onCreateDialogPositiveClick( + binding.account, binding.groupChatName.getText().toString().trim()); + return true; + }); return builder.create(); } - public interface CreateConferenceDialogListener { void onCreateDialogPositiveClick(AutoCompleteTextView spinner, String subject); } @@ -68,8 +76,8 @@ public class CreatePrivateGroupChatDialog extends DialogFragment { try { mListener = (CreateConferenceDialogListener) context; } catch (ClassCastException e) { - throw new ClassCastException(context.toString() - + " must implement CreateConferenceDialogListener"); + throw new ClassCastException( + context + " must implement CreateConferenceDialogListener"); } } diff --git a/src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java b/src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java index b12aba750cb0accb3a0ace23dccf6000425ba759..7351823f6708e2e87ad6073f092e64da84b04f8e 100644 --- a/src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java +++ b/src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java @@ -288,7 +288,7 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke mListener = (CreatePublicChannelDialogListener) context; } catch (ClassCastException e) { throw new ClassCastException( - context.toString() + " must implement CreateConferenceDialogListener"); + context + " must implement CreateConferenceDialogListener"); } } diff --git a/src/main/java/eu/siacs/conversations/ui/JoinConferenceDialog.java b/src/main/java/eu/siacs/conversations/ui/JoinConferenceDialog.java index b78199c15e09fb1f3608ce573dc42656d43f0b5f..3057749a317b74aa072ec443c203eab1d9e64d7a 100644 --- a/src/main/java/eu/siacs/conversations/ui/JoinConferenceDialog.java +++ b/src/main/java/eu/siacs/conversations/ui/JoinConferenceDialog.java @@ -6,120 +6,140 @@ import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; import android.widget.AutoCompleteTextView; -import android.widget.Spinner; - import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.databinding.DataBindingUtil; import androidx.fragment.app.DialogFragment; - import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.textfield.TextInputLayout; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.DialogJoinConferenceBinding; import eu.siacs.conversations.ui.adapter.KnownHostsAdapter; import eu.siacs.conversations.ui.interfaces.OnBackendConnected; import eu.siacs.conversations.ui.util.DelayedHintHelper; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; public class JoinConferenceDialog extends DialogFragment implements OnBackendConnected { - private static final String PREFILLED_JID_KEY = "prefilled_jid"; - private static final String PREFILLED_PASSWORD_KEY = "prefilled_password"; - private static final String ACCOUNTS_LIST_KEY = "activated_accounts_list"; - private JoinConferenceDialogListener mListener; - private KnownHostsAdapter knownHostsAdapter; - - public static JoinConferenceDialog newInstance(String prefilledJid, String password, List accounts) { - JoinConferenceDialog dialog = new JoinConferenceDialog(); - Bundle bundle = new Bundle(); - bundle.putString(PREFILLED_JID_KEY, prefilledJid); - bundle.putString(PREFILLED_PASSWORD_KEY, password); - bundle.putStringArrayList(ACCOUNTS_LIST_KEY, (ArrayList) accounts); - dialog.setArguments(bundle); - return dialog; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - setRetainInstance(true); - } - - @NonNull - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity()); - builder.setTitle(R.string.join_public_channel); - final DialogJoinConferenceBinding binding = DataBindingUtil.inflate(getActivity().getLayoutInflater(), R.layout.dialog_join_conference, null, false); - DelayedHintHelper.setHint(R.string.channel_full_jid_example, binding.jid); - this.knownHostsAdapter = new KnownHostsAdapter(getActivity(), R.layout.item_autocomplete); - binding.jid.setAdapter(knownHostsAdapter); - String prefilledJid = getArguments().getString(PREFILLED_JID_KEY); - if (prefilledJid != null) { - binding.jid.append(prefilledJid); - } - StartConversationActivity.populateAccountSpinner(getActivity(), getArguments().getStringArrayList(ACCOUNTS_LIST_KEY), binding.account); - builder.setView(binding.getRoot()); - builder.setPositiveButton(R.string.join, null); - builder.setNegativeButton(R.string.cancel, null); - AlertDialog dialog = builder.create(); - dialog.show(); - dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(view -> mListener.onJoinDialogPositiveClick(dialog, binding.account, binding.accountJidLayout, binding.jid, binding.jid.getText().toString().equals(getArguments().getString(PREFILLED_JID_KEY)) ? getArguments().getString(PREFILLED_PASSWORD_KEY) : null)); - binding.jid.setOnEditorActionListener((v, actionId, event) -> { - mListener.onJoinDialogPositiveClick(dialog, binding.account, binding.accountJidLayout, binding.jid, binding.jid.getText().toString().equals(getArguments().getString(PREFILLED_JID_KEY)) ? getArguments().getString(PREFILLED_PASSWORD_KEY) : null); - return true; - }); - return dialog; - } - - @Override - public void onBackendConnected() { - refreshKnownHosts(); - } - - private void refreshKnownHosts() { - Activity activity = getActivity(); - if (activity instanceof XmppActivity) { - Collection hosts = ((XmppActivity) activity).xmppConnectionService.getKnownConferenceHosts(); - this.knownHostsAdapter.refresh(hosts); - } - } - - @Override - public void onAttach(@NonNull final Context context) { - super.onAttach(context); - try { - mListener = (JoinConferenceDialogListener) context; - } catch (ClassCastException e) { - throw new ClassCastException(context.toString() - + " must implement JoinConferenceDialogListener"); - } - } - - @Override - public void onDestroyView() { - Dialog dialog = getDialog(); - if (dialog != null && getRetainInstance()) { - dialog.setDismissMessage(null); - } - super.onDestroyView(); - } - - @Override - public void onStart() { - super.onStart(); - final Activity activity = getActivity(); - if (activity instanceof XmppActivity && ((XmppActivity) activity).xmppConnectionService != null) { - refreshKnownHosts(); - } - } - - public interface JoinConferenceDialogListener { - void onJoinDialogPositiveClick(Dialog dialog, AutoCompleteTextView spinner, TextInputLayout jidLayout, AutoCompleteTextView jid, String password); - } + private static final String PREFILLED_JID_KEY = "prefilled_jid"; + private static final String PREFILLED_PASSWORD_KEY = "prefilled_password"; + private static final String ACCOUNTS_LIST_KEY = "activated_accounts_list"; + private JoinConferenceDialogListener mListener; + private KnownHostsAdapter knownHostsAdapter; + + public static JoinConferenceDialog newInstance(String prefilledJid, String password, List accounts) { + JoinConferenceDialog dialog = new JoinConferenceDialog(); + Bundle bundle = new Bundle(); + bundle.putString(PREFILLED_JID_KEY, prefilledJid); + bundle.putString(PREFILLED_PASSWORD_KEY, password); + bundle.putStringArrayList(ACCOUNTS_LIST_KEY, (ArrayList) accounts); + dialog.setArguments(bundle); + return dialog; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + setRetainInstance(true); + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final MaterialAlertDialogBuilder builder = + new MaterialAlertDialogBuilder(requireActivity()); + builder.setTitle(R.string.join_public_channel); + final DialogJoinConferenceBinding binding = + DataBindingUtil.inflate( + getActivity().getLayoutInflater(), + R.layout.dialog_join_conference, + null, + false); + DelayedHintHelper.setHint(R.string.channel_full_jid_example, binding.jid); + this.knownHostsAdapter = new KnownHostsAdapter(getActivity(), R.layout.item_autocomplete); + binding.jid.setAdapter(knownHostsAdapter); + String prefilledJid = getArguments().getString(PREFILLED_JID_KEY); + if (prefilledJid != null) { + binding.jid.append(prefilledJid); + } + StartConversationActivity.populateAccountSpinner( + getActivity(), + getArguments().getStringArrayList(ACCOUNTS_LIST_KEY), + binding.account); + builder.setView(binding.getRoot()); + builder.setPositiveButton(R.string.join, null); + builder.setNegativeButton(R.string.cancel, null); + AlertDialog dialog = builder.create(); + dialog.show(); + + dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(view -> mListener.onJoinDialogPositiveClick(dialog, binding.account, binding.accountJidLayout, binding.jid, binding.jid.getText().toString().equals(getArguments().getString(PREFILLED_JID_KEY)) ? getArguments().getString(PREFILLED_PASSWORD_KEY) : null)); + dialog.getButton(DialogInterface.BUTTON_POSITIVE) + .setOnClickListener( + view -> + mListener.onJoinDialogPositiveClick( + dialog, + binding.account, + binding.accountJidLayout, + binding.jid, + binding.jid.getText().toString().equals(getArguments().getString(PREFILLED_JID_KEY)) ? getArguments().getString(PREFILLED_PASSWORD_KEY) : null + )); + binding.jid.setOnEditorActionListener( + (v, actionId, event) -> { + mListener.onJoinDialogPositiveClick( + dialog, binding.account, binding.accountJidLayout, binding.jid, + binding.jid.getText().toString().equals(getArguments().getString(PREFILLED_JID_KEY)) ? getArguments().getString(PREFILLED_PASSWORD_KEY) : null + ); + return true; + }); + return dialog; + } + + @Override + public void onBackendConnected() { + refreshKnownHosts(); + } + + private void refreshKnownHosts() { + Activity activity = getActivity(); + if (activity instanceof XmppActivity) { + Collection hosts = + ((XmppActivity) activity).xmppConnectionService.getKnownConferenceHosts(); + this.knownHostsAdapter.refresh(hosts); + } + } + + @Override + public void onAttach(@NonNull final Context context) { + super.onAttach(context); + try { + mListener = (JoinConferenceDialogListener) context; + } catch (ClassCastException e) { + throw new ClassCastException(context + " must implement JoinConferenceDialogListener"); + } + } + + @Override + public void onDestroyView() { + Dialog dialog = getDialog(); + if (dialog != null && getRetainInstance()) { + dialog.setDismissMessage(null); + } + super.onDestroyView(); + } + + @Override + public void onStart() { + super.onStart(); + final Activity activity = getActivity(); + if (activity instanceof XmppActivity + && ((XmppActivity) activity).xmppConnectionService != null) { + refreshKnownHosts(); + } + } + + public interface JoinConferenceDialogListener { + void onJoinDialogPositiveClick(Dialog dialog, AutoCompleteTextView spinner, TextInputLayout jidLayout, AutoCompleteTextView jid, String password); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/ShortcutActivity.java b/src/main/java/eu/siacs/conversations/ui/ShortcutActivity.java index 66b3fb886f93bdf5da2cce289f5eb37250fa8f96..eab1a705082feea4fca22488d21fd7abf436448b 100644 --- a/src/main/java/eu/siacs/conversations/ui/ShortcutActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ShortcutActivity.java @@ -5,50 +5,57 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.view.inputmethod.InputMethodManager; - import androidx.appcompat.app.ActionBar; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.ListItem; +import java.util.Collections; +import java.util.List; public class ShortcutActivity extends AbstractSearchableListItemActivity { - private static final List BLACKLISTED_ACTIVITIES = Arrays.asList("com.teslacoilsw.launcher.ChooseActionIntentActivity"); + private static final List BLACKLISTED_ACTIVITIES = + List.of("com.teslacoilsw.launcher.ChooseActionIntentActivity"); @Override - protected void refreshUiReal() { - - } + protected void refreshUiReal() {} @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - getListView().setOnItemClickListener((parent, view, position, id) -> { - - final ComponentName callingActivity = getCallingActivity(); + getListView() + .setOnItemClickListener( + (parent, view, position, id) -> { + final ComponentName callingActivity = getCallingActivity(); - final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(getSearchEditText().getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY); + final InputMethodManager imm = + (InputMethodManager) + getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow( + getSearchEditText().getWindowToken(), + InputMethodManager.HIDE_IMPLICIT_ONLY); - ListItem listItem = getListItems().get(position); - final boolean legacy = BLACKLISTED_ACTIVITIES.contains(callingActivity == null ? null : callingActivity.getClassName()); - Intent shortcut = xmppConnectionService.getShortcutService().createShortcut(((Contact) listItem), legacy); - setResult(RESULT_OK,shortcut); - finish(); - }); + ListItem listItem = getListItems().get(position); + final boolean legacy = + BLACKLISTED_ACTIVITIES.contains( + callingActivity == null + ? null + : callingActivity.getClassName()); + Intent shortcut = + xmppConnectionService + .getShortcutService() + .createShortcut(((Contact) listItem), legacy); + setResult(RESULT_OK, shortcut); + finish(); + }); } @Override public void onStart() { super.onStart(); ActionBar bar = getSupportActionBar(); - if(bar != null){ + if (bar != null) { bar.setTitle(R.string.create_shortcut); } } @@ -63,8 +70,7 @@ public class ShortcutActivity extends AbstractSearchableListItemActivity { for (final Account account : xmppConnectionService.getAccounts()) { if (account.isEnabled()) { for (final Contact contact : account.getRoster().getContacts()) { - if (contact.showInContactList() - && contact.match(this, needle)) { + if (contact.showInContactList() && contact.match(this, needle)) { getListItems().add(contact); } } diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 1d09685e1fcfc69e9df27d147a19c0928899c13d..f8278558ca67d8c9d2bf2aed08f3c3fc960a2c75 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -216,8 +216,7 @@ public abstract class XmppActivity extends ActionBarActivity { private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { if (imageView != null) { final Drawable drawable = imageView.getDrawable(); - if (drawable instanceof AsyncDrawable) { - final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; + if (drawable instanceof AsyncDrawable asyncDrawable) { return asyncDrawable.getBitmapWorkerTask(); } } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java index 88f7636bfa612fecfe44661104fd717af37896e6..fd9869f6bfa3b8ac7caedba76183d9844bfcbbc3 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java @@ -30,7 +30,7 @@ public class KnownHostsAdapter extends ArrayAdapter { final String local = split[0].toLowerCase(Locale.ENGLISH); if (Config.QUICKSY_DOMAIN != null && E164_PATTERN.matcher(local).matches()) { - builder.add(local + '@' + Config.QUICKSY_DOMAIN.toString()); + builder.add(local + '@' + Config.QUICKSY_DOMAIN); } else { for (String domain : domains) { builder.add(local + '@' + domain); diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java index 91048df4e3e03efa1bce05a77f2ecddbf7103746..066588844670d9f7817ce1d13276b6ce04924c88 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java @@ -79,10 +79,8 @@ public class MediaAdapter extends RecyclerView.Adapter { private abstract static class MessageItemViewHolder /*extends RecyclerView.ViewHolder*/ { - private View itemView; + private final View itemView; private MessageItemViewHolder(@NonNull View itemView) { this.itemView = itemView; diff --git a/src/main/java/eu/siacs/conversations/ui/service/CameraManager.java b/src/main/java/eu/siacs/conversations/ui/service/CameraManager.java index f3af5d3ed0823f374cb4b9bd4aafa0337b4a905e..a0b3e216f7706dad5bc936bca5a5f38aa694764e 100644 --- a/src/main/java/eu/siacs/conversations/ui/service/CameraManager.java +++ b/src/main/java/eu/siacs/conversations/ui/service/CameraManager.java @@ -25,9 +25,8 @@ import android.hardware.Camera.CameraInfo; import android.hardware.Camera.PreviewCallback; import android.util.Log; import android.view.TextureView; - import com.google.zxing.PlanarYUVLuminanceSource; - +import eu.siacs.conversations.Config; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; @@ -35,8 +34,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; -import eu.siacs.conversations.Config; - /** * @author Andreas Schildbach */ @@ -69,7 +66,10 @@ public final class CameraManager { return cameraInfo.orientation; } - public Camera open(final TextureView textureView, final int displayOrientation, final boolean continuousAutoFocus) + public Camera open( + final TextureView textureView, + final int displayOrientation, + final boolean continuousAutoFocus) throws IOException { final int cameraId = determineCameraId(); Camera.getCameraInfo(cameraId, cameraInfo); @@ -80,8 +80,7 @@ public final class CameraManager { camera.setDisplayOrientation((720 - displayOrientation - cameraInfo.orientation) % 360); else if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) camera.setDisplayOrientation((720 - displayOrientation + cameraInfo.orientation) % 360); - else - throw new IllegalStateException("facing: " + cameraInfo.facing); + else throw new IllegalStateException("facing: " + cameraInfo.facing); camera.setPreviewTexture(textureView.getSurfaceTexture()); @@ -105,18 +104,22 @@ public final class CameraManager { boolean isTexturePortrait = width < height; boolean isCameraPortrait = cameraResolution.width < cameraResolution.height; if (isTexturePortrait == isCameraPortrait) { - widthFactor = (float)cameraResolution.width / width; - heightFactor = (float)cameraResolution.height / height; + widthFactor = (float) cameraResolution.width / width; + heightFactor = (float) cameraResolution.height / height; orientedFrame = new Rect(frame); } else { - widthFactor = (float)cameraResolution.width / height; - heightFactor = (float)cameraResolution.height / width; + widthFactor = (float) cameraResolution.width / height; + heightFactor = (float) cameraResolution.height / width; // Swap X and Y coordinates to flip frame to the same orientation as cameraResolution orientedFrame = new Rect(frame.top, frame.left, frame.bottom, frame.right); } - framePreview = new RectF(orientedFrame.left * widthFactor, orientedFrame.top * heightFactor, - orientedFrame.right * widthFactor, orientedFrame.bottom * heightFactor); + framePreview = + new RectF( + orientedFrame.left * widthFactor, + orientedFrame.top * heightFactor, + orientedFrame.right * widthFactor, + orientedFrame.bottom * heightFactor); final String savedParameters = parameters == null ? null : parameters.flatten(); @@ -130,7 +133,7 @@ public final class CameraManager { camera.setParameters(parameters2); setDesiredCameraParameters(camera, cameraResolution, continuousAutoFocus); } catch (final RuntimeException x2) { - Log.d(Config.LOGTAG,"problem setting camera parameters", x2); + Log.d(Config.LOGTAG, "problem setting camera parameters", x2); } } } @@ -139,7 +142,7 @@ public final class CameraManager { camera.startPreview(); return camera; } catch (final RuntimeException x) { - Log.w(Config.LOGTAG,"something went wrong while starting camera preview", x); + Log.w(Config.LOGTAG, "something went wrong while starting camera preview", x); camera.release(); throw x; } @@ -152,15 +155,13 @@ public final class CameraManager { // prefer back-facing camera for (int i = 0; i < cameraCount; i++) { Camera.getCameraInfo(i, cameraInfo); - if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) - return i; + if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) return i; } // fall back to front-facing camera for (int i = 0; i < cameraCount; i++) { Camera.getCameraInfo(i, cameraInfo); - if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) - return i; + if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) return i; } return -1; @@ -171,29 +172,28 @@ public final class CameraManager { try { camera.stopPreview(); } catch (final RuntimeException x) { - Log.w(Config.LOGTAG,"something went wrong while stopping camera preview", x); + Log.w(Config.LOGTAG, "something went wrong while stopping camera preview", x); } camera.release(); } } - private static final Comparator numPixelComparator = new Comparator() { - @Override - public int compare(final Camera.Size size1, final Camera.Size size2) { - final int pixels1 = size1.height * size1.width; - final int pixels2 = size2.height * size2.width; - - if (pixels1 < pixels2) - return 1; - else if (pixels1 > pixels2) - return -1; - else - return 0; - } - }; + private static final Comparator numPixelComparator = + new Comparator() { + @Override + public int compare(final Camera.Size size1, final Camera.Size size2) { + final int pixels1 = size1.height * size1.width; + final int pixels2 = size2.height * size2.width; + + if (pixels1 < pixels2) return 1; + else if (pixels1 > pixels2) return -1; + else return 0; + } + }; - private static Camera.Size findBestPreviewSizeValue(final Camera.Parameters parameters, int width, int height) { + private static Camera.Size findBestPreviewSizeValue( + final Camera.Parameters parameters, int width, int height) { if (height > width) { final int temp = width; width = height; @@ -203,11 +203,11 @@ public final class CameraManager { final float screenAspectRatio = (float) width / (float) height; final List rawSupportedSizes = parameters.getSupportedPreviewSizes(); - if (rawSupportedSizes == null) - return parameters.getPreviewSize(); + if (rawSupportedSizes == null) return parameters.getPreviewSize(); // sort by size, descending - final List supportedPreviewSizes = new ArrayList(rawSupportedSizes); + final List supportedPreviewSizes = + new ArrayList(rawSupportedSizes); Collections.sort(supportedPreviewSizes, numPixelComparator); Camera.Size bestSize = null; @@ -217,8 +217,7 @@ public final class CameraManager { final int realWidth = supportedPreviewSize.width; final int realHeight = supportedPreviewSize.height; final int realPixels = realWidth * realHeight; - if (realPixels < MIN_PREVIEW_PIXELS || realPixels > MAX_PREVIEW_PIXELS) - continue; + if (realPixels < MIN_PREVIEW_PIXELS || realPixels > MAX_PREVIEW_PIXELS) continue; final boolean isCandidatePortrait = realWidth < realHeight; final int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth; @@ -234,27 +233,32 @@ public final class CameraManager { } } - if (bestSize != null) - return bestSize; - else - return parameters.getPreviewSize(); + if (bestSize != null) return bestSize; + else return parameters.getPreviewSize(); } @SuppressLint("InlinedApi") - private static void setDesiredCameraParameters(final Camera camera, final Camera.Size cameraResolution, + private static void setDesiredCameraParameters( + final Camera camera, + final Camera.Size cameraResolution, final boolean continuousAutoFocus) { final Camera.Parameters parameters = camera.getParameters(); - if (parameters == null) - return; + if (parameters == null) return; final List supportedFocusModes = parameters.getSupportedFocusModes(); - final String focusMode = continuousAutoFocus - ? findValue(supportedFocusModes, Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE, - Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, Camera.Parameters.FOCUS_MODE_AUTO, - Camera.Parameters.FOCUS_MODE_MACRO) - : findValue(supportedFocusModes, Camera.Parameters.FOCUS_MODE_AUTO, Camera.Parameters.FOCUS_MODE_MACRO); - if (focusMode != null) - parameters.setFocusMode(focusMode); + final String focusMode = + continuousAutoFocus + ? findValue( + supportedFocusModes, + Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE, + Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, + Camera.Parameters.FOCUS_MODE_AUTO, + Camera.Parameters.FOCUS_MODE_MACRO) + : findValue( + supportedFocusModes, + Camera.Parameters.FOCUS_MODE_AUTO, + Camera.Parameters.FOCUS_MODE_MACRO); + if (focusMode != null) parameters.setFocusMode(focusMode); parameters.setPreviewSize(cameraResolution.width, cameraResolution.height); @@ -265,26 +269,31 @@ public final class CameraManager { try { camera.setOneShotPreviewCallback(callback); } catch (final RuntimeException x) { - Log.d(Config.LOGTAG,"problem requesting preview frame, callback won't be called", x); + Log.d(Config.LOGTAG, "problem requesting preview frame, callback won't be called", x); } } public PlanarYUVLuminanceSource buildLuminanceSource(final byte[] data) { - return new PlanarYUVLuminanceSource(data, cameraResolution.width, cameraResolution.height, - (int) framePreview.left, (int) framePreview.top, (int) framePreview.width(), - (int) framePreview.height(), false); + return new PlanarYUVLuminanceSource( + data, + cameraResolution.width, + cameraResolution.height, + (int) framePreview.left, + (int) framePreview.top, + (int) framePreview.width(), + (int) framePreview.height(), + false); } public void setTorch(final boolean enabled) { - if (enabled != getTorchEnabled(camera)) - setTorchEnabled(camera, enabled); + if (enabled != getTorchEnabled(camera)) setTorchEnabled(camera, enabled); } private static boolean getTorchEnabled(final Camera camera) { final Camera.Parameters parameters = camera.getParameters(); if (parameters != null) { final String flashMode = camera.getParameters().getFlashMode(); - return flashMode != null && (Camera.Parameters.FLASH_MODE_ON.equals(flashMode) + return (Camera.Parameters.FLASH_MODE_ON.equals(flashMode) || Camera.Parameters.FLASH_MODE_TORCH.equals(flashMode)); } @@ -298,10 +307,12 @@ public final class CameraManager { if (supportedFlashModes != null) { final String flashMode; if (enabled) - flashMode = findValue(supportedFlashModes, Camera.Parameters.FLASH_MODE_TORCH, - Camera.Parameters.FLASH_MODE_ON); - else - flashMode = findValue(supportedFlashModes, Camera.Parameters.FLASH_MODE_OFF); + flashMode = + findValue( + supportedFlashModes, + Camera.Parameters.FLASH_MODE_TORCH, + Camera.Parameters.FLASH_MODE_ON); + else flashMode = findValue(supportedFlashModes, Camera.Parameters.FLASH_MODE_OFF); if (flashMode != null) { camera.cancelAutoFocus(); // autofocus can cause conflict @@ -314,8 +325,7 @@ public final class CameraManager { private static String findValue(final Collection values, final String... valuesToFind) { for (final String valueToFind : valuesToFind) - if (values.contains(valueToFind)) - return valueToFind; + if (values.contains(valueToFind)) return valueToFind; return null; } diff --git a/src/main/java/eu/siacs/conversations/ui/util/AvatarWorkerTask.java b/src/main/java/eu/siacs/conversations/ui/util/AvatarWorkerTask.java index 51617da155dffe77cf7421a75687f3a691b030be..4ea40751e9fc3e5500354f911a35c9e4cf563968 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/AvatarWorkerTask.java +++ b/src/main/java/eu/siacs/conversations/ui/util/AvatarWorkerTask.java @@ -9,23 +9,19 @@ import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Build; import android.widget.ImageView; - import androidx.annotation.DimenRes; - -import java.lang.ref.WeakReference; -import java.util.concurrent.RejectedExecutionException; - import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.AvatarService; import eu.siacs.conversations.ui.XmppActivity; +import java.lang.ref.WeakReference; +import java.util.concurrent.RejectedExecutionException; public class AvatarWorkerTask extends AsyncTask { private final WeakReference imageViewReference; private final WeakReference activityReference; private AvatarService.Avatarable avatarable = null; - private @DimenRes - final int size; + private @DimenRes final int size; public AvatarWorkerTask(ImageView imageView, @DimenRes int size) { imageViewReference = new WeakReference<>(imageView); @@ -47,7 +43,8 @@ public class AvatarWorkerTask extends AsyncTask(items.toArray(new CharSequence[items.size()]), actions.toArray(new Integer[actions.size()])); } - public static void configureMucDetailsContextMenu(XmppActivity activity, Menu menu, Conversation conversation, User user) { + public static void configureMucDetailsContextMenu( + XmppActivity activity, Menu menu, Conversation conversation, User user) { final MucOptions mucOptions = conversation.getMucOptions(); - final boolean advancedMode = PreferenceManager.getDefaultSharedPreferences(activity).getBoolean("advanced_muc_mode", false); + final boolean advancedMode = + PreferenceManager.getDefaultSharedPreferences(activity) + .getBoolean("advanced_muc_mode", false); final boolean showMucPm = PreferenceManager.getDefaultSharedPreferences(activity).getBoolean("show_muc_pm", false); final boolean isGroupChat = mucOptions.isPrivateAndNonAnonymous(); MenuItem sendPrivateMessage = menu.findItem(R.id.send_private_message); @@ -136,15 +138,19 @@ public final class MucDetailsContextMenuHelper { MenuItem startConversation = menu.findItem(R.id.start_conversation); MenuItem removeFromRoom = menu.findItem(R.id.remove_from_room); MenuItem managePermissions = menu.findItem(R.id.manage_permissions); - removeFromRoom.setTitle(isGroupChat ? R.string.remove_from_room : R.string.remove_from_channel); + removeFromRoom.setTitle( + isGroupChat ? R.string.remove_from_room : R.string.remove_from_channel); MenuItem invite = menu.findItem(R.id.invite); startConversation.setVisible(true); final Contact contact = user.getContact(); final User self = conversation.getMucOptions().getSelf(); - if ((contact != null && contact.showInRoster()) || mucOptions.isPrivateAndNonAnonymous()) { + if ((contact != null && contact.showInRoster()) + || mucOptions.isPrivateAndNonAnonymous()) { showContactDetails.setVisible(contact == null || !contact.isSelf()); } - if ((activity instanceof ConferenceDetailsActivity || activity instanceof MucUsersActivity) && user.getRole() == MucOptions.Role.NONE) { + if ((activity instanceof ConferenceDetailsActivity + || activity instanceof MucUsersActivity) + && user.getRole() == MucOptions.Role.NONE) { invite.setVisible(true); } boolean managePermissionsVisible = false; @@ -202,15 +208,20 @@ public final class MucDetailsContextMenuHelper { .setNegativeButton(R.string.no, null).show(); } - public static boolean onContextItemSelected(MenuItem item, User user, XmppActivity activity, final String fingerprint) { + public static boolean onContextItemSelected( + MenuItem item, User user, XmppActivity activity, final String fingerprint) { final Conversation conversation = user.getConversation(); - final XmppConnectionService.OnAffiliationChanged onAffiliationChanged = activity instanceof XmppConnectionService.OnAffiliationChanged ? (XmppConnectionService.OnAffiliationChanged) activity : null; + final XmppConnectionService.OnAffiliationChanged onAffiliationChanged = + activity instanceof XmppConnectionService.OnAffiliationChanged + ? (XmppConnectionService.OnAffiliationChanged) activity + : null; Jid jid = user.getRealJid(); switch (item.getItemId()) { case R.id.action_contact_details: final Jid realJid = user.getRealJid(); final Account account = conversation.getAccount(); - final Contact contact = realJid == null ? null : account.getRoster().getContact(realJid); + final Contact contact = + realJid == null ? null : account.getRoster().getContact(realJid); if (contact != null) { activity.switchToContactDetails(contact, fingerprint); } @@ -320,37 +331,60 @@ public final class MucDetailsContextMenuHelper { } } - private static void removeFromRoom(final User user, XmppActivity activity, XmppConnectionService.OnAffiliationChanged onAffiliationChanged) { + private static void removeFromRoom( + final User user, + XmppActivity activity, + XmppConnectionService.OnAffiliationChanged onAffiliationChanged) { final Conversation conversation = user.getConversation(); if (conversation.getMucOptions().membersOnly()) { - activity.xmppConnectionService.changeAffiliationInConference(conversation, user.getRealJid(), MucOptions.Affiliation.NONE, onAffiliationChanged); + activity.xmppConnectionService.changeAffiliationInConference( + conversation, + user.getRealJid(), + MucOptions.Affiliation.NONE, + onAffiliationChanged); if (user.getRole() != MucOptions.Role.NONE) { - activity.xmppConnectionService.changeRoleInConference(conversation, user.getName(), MucOptions.Role.NONE); + activity.xmppConnectionService.changeRoleInConference( + conversation, user.getName(), MucOptions.Role.NONE); } } else { final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity); builder.setTitle(R.string.ban_from_conference); String jid = user.getRealJid().asBareJid().toString(); - SpannableString message = new SpannableString(activity.getString(R.string.removing_from_public_conference, jid)); + SpannableString message = + new SpannableString( + activity.getString(R.string.removing_from_public_conference, jid)); int start = message.toString().indexOf(jid); if (start >= 0) { - message.setSpan(new TypefaceSpan("monospace"), start, start + jid.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + message.setSpan( + new TypefaceSpan("monospace"), + start, + start + jid.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } builder.setMessage(message); builder.setNegativeButton(R.string.cancel, null); - builder.setPositiveButton(R.string.ban_now, (dialog, which) -> { - activity.xmppConnectionService.changeAffiliationInConference(conversation, user.getRealJid(), MucOptions.Affiliation.OUTCAST, onAffiliationChanged); - if (user.getRole() != MucOptions.Role.NONE) { - activity.xmppConnectionService.changeRoleInConference(conversation, user.getName(), MucOptions.Role.NONE); - } - }); + builder.setPositiveButton( + R.string.ban_now, + (dialog, which) -> { + activity.xmppConnectionService.changeAffiliationInConference( + conversation, + user.getRealJid(), + MucOptions.Affiliation.OUTCAST, + onAffiliationChanged); + if (user.getRole() != MucOptions.Role.NONE) { + activity.xmppConnectionService.changeRoleInConference( + conversation, user.getName(), MucOptions.Role.NONE); + } + }); builder.create().show(); } } private static void startConversation(User user, XmppActivity activity) { if (user.getRealJid() != null) { - Conversation newConversation = activity.xmppConnectionService.findOrCreateConversation(user.getAccount(), user.getRealJid().asBareJid(), false, true); + Conversation newConversation = + activity.xmppConnectionService.findOrCreateConversation( + user.getAccount(), user.getRealJid().asBareJid(), false, true); activity.switchToConversation(newConversation); } } diff --git a/src/main/java/eu/siacs/conversations/ui/widget/ScannerView.java b/src/main/java/eu/siacs/conversations/ui/widget/ScannerView.java index b755ae516e7ba7b9ff24155583ce3f9b0868a344..826a637d256be8834c466089278d4bc473aea775 100644 --- a/src/main/java/eu/siacs/conversations/ui/widget/ScannerView.java +++ b/src/main/java/eu/siacs/conversations/ui/widget/ScannerView.java @@ -28,22 +28,18 @@ import android.graphics.Rect; import android.graphics.RectF; import android.util.AttributeSet; import android.view.View; - import androidx.core.content.ContextCompat; - import com.google.zxing.ResultPoint; - +import eu.siacs.conversations.R; import java.util.HashMap; import java.util.Iterator; import java.util.Map; -import eu.siacs.conversations.R; - /** * @author Andreas Schildbach */ public class ScannerView extends View { - private static final long LASER_ANIMATION_DELAY_MS = 100l; + private static final long LASER_ANIMATION_DELAY_MS = 100L; private static final int DOT_OPACITY = 0xa0; private static final int DOT_TTL_MS = 500; @@ -81,8 +77,12 @@ public class ScannerView extends View { dotPaint.setAntiAlias(true); } - public void setFraming(final Rect frame, final RectF framePreview, final int displayRotation, - final int cameraRotation, final boolean cameraFlip) { + public void setFraming( + final Rect frame, + final RectF framePreview, + final int displayRotation, + final int cameraRotation, + final boolean cameraFlip) { this.frame = frame; matrix.setRectToRect(framePreview, new RectF(frame), ScaleToFit.FILL); matrix.postRotate(-displayRotation, frame.exactCenterX(), frame.exactCenterY()); @@ -99,15 +99,14 @@ public class ScannerView extends View { } public void addDot(final ResultPoint dot) { - dots.put(new float[] { dot.getX(), dot.getY() }, System.currentTimeMillis()); + dots.put(new float[] {dot.getX(), dot.getY()}, System.currentTimeMillis()); invalidate(); } @Override public void onDraw(final Canvas canvas) { - if (frame == null) - return; + if (frame == null) return; final long now = System.currentTimeMillis(); @@ -142,7 +141,8 @@ public class ScannerView extends View { canvas.drawRect(frame, laserPaint); // draw points - for (final Iterator> i = dots.entrySet().iterator(); i.hasNext();) { + for (final Iterator> i = dots.entrySet().iterator(); + i.hasNext(); ) { final Map.Entry entry = i.next(); final long age = now - entry.getValue(); if (age < DOT_TTL_MS) { diff --git a/src/main/java/eu/siacs/conversations/utils/CursorUtils.java b/src/main/java/eu/siacs/conversations/utils/CursorUtils.java index 91befa22936a1912801662c9cd74e692c2ee9b7c..23e8401cd101988f38ee6b529db704a616537bcc 100644 --- a/src/main/java/eu/siacs/conversations/utils/CursorUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/CursorUtils.java @@ -9,8 +9,7 @@ public class CursorUtils { public static void upgradeCursorWindowSize(final Cursor cursor) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) { - if (cursor instanceof AbstractWindowedCursor) { - final AbstractWindowedCursor windowedCursor = (AbstractWindowedCursor) cursor; + if (cursor instanceof AbstractWindowedCursor windowedCursor) { windowedCursor.setWindow(new CursorWindow("4M", 4 * 1024 * 1024)); } if (cursor instanceof SQLiteCursor) { @@ -18,5 +17,4 @@ public class CursorUtils { } } } - } diff --git a/src/main/java/eu/siacs/conversations/utils/GeoHelper.java b/src/main/java/eu/siacs/conversations/utils/GeoHelper.java index 648ad049b5761592ba0e525a747edd916846d9b2..db8e3136e7d9269cab72bfb8e3323ea932a96467 100644 --- a/src/main/java/eu/siacs/conversations/utils/GeoHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/GeoHelper.java @@ -12,8 +12,8 @@ import eu.siacs.conversations.entities.Conversational; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.ui.ShareLocationActivity; import eu.siacs.conversations.ui.ShowLocationActivity; -import java.io.UnsupportedEncodingException; import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.regex.Matcher; import org.osmdroid.util.GeoPoint; @@ -160,11 +160,8 @@ public class GeoHelper { private static String getLabel(Context context, Message message) { if (message.getStatus() == Message.STATUS_RECEIVED) { - try { - return URLEncoder.encode(UIHelper.getMessageDisplayName(message), "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new AssertionError(e); - } + return URLEncoder.encode( + UIHelper.getMessageDisplayName(message), StandardCharsets.UTF_8); } else { return context.getString(R.string.me); } diff --git a/src/main/java/eu/siacs/conversations/utils/ImStyleParser.java b/src/main/java/eu/siacs/conversations/utils/ImStyleParser.java index 28910d4d9a5a47dfdda712332dc9ac9ae80d4415..199c18b3baaa17389e65963c0ce3e3f14010f6f7 100644 --- a/src/main/java/eu/siacs/conversations/utils/ImStyleParser.java +++ b/src/main/java/eu/siacs/conversations/utils/ImStyleParser.java @@ -35,11 +35,11 @@ import java.util.List; public class ImStyleParser { - private final static List KEYWORDS = Arrays.asList('*', '_', '~', '`'); - private final static List NO_SUB_PARSING_KEYWORDS = Arrays.asList('`'); - private final static List BLOCK_KEYWORDS = Arrays.asList('`'); - private final static boolean ALLOW_EMPTY = false; - private final static boolean PARSE_HIGHER_ORDER_END = true; + private static final List KEYWORDS = Arrays.asList('*', '_', '~', '`'); + private static final List NO_SUB_PARSING_KEYWORDS = List.of('`'); + private static final List BLOCK_KEYWORDS = List.of('`'); + private static final boolean ALLOW_EMPTY = false; + private static final boolean PARSE_HIGHER_ORDER_END = true; public static List