From 355755e690f1c0faf590e61ce672f470d96341ef Mon Sep 17 00:00:00 2001 From: Daniel Gultsch <daniel@gultsch.de> Date: Fri, 24 Jan 2025 18:26:30 +0100 Subject: [PATCH] get rid of jid escaping api Conversations never supported Jid Escaping but the underlaying library did so we had API for that which could lead to confusion --- .../entities/AccountConfiguration.java | 20 +- .../services/ImportBackupService.java | 27 +- .../ui/EasyOnboardingInviteActivity.java | 7 +- .../conversations/ui/MagicCreateActivity.java | 18 +- .../ui/ManageAccountActivity.java | 18 +- .../conversations/ui/WelcomeActivity.java | 18 +- .../utils/ProvisioningUtils.java | 12 +- .../conversations/utils/SignupUtils.java | 16 +- .../conversations/crypto/sasl/External.java | 6 +- .../siacs/conversations/entities/Account.java | 22 +- .../conversations/entities/Bookmark.java | 521 ++++--- .../siacs/conversations/entities/Contact.java | 82 +- .../conversations/entities/Conversation.java | 3 +- .../conversations/entities/MucOptions.java | 105 +- .../conversations/entities/RawBlockable.java | 17 +- .../conversations/entities/Reaction.java | 8 +- .../eu/siacs/conversations/entities/Room.java | 16 +- .../conversations/generator/IqGenerator.java | 102 +- .../conversations/parser/AbstractParser.java | 333 ++-- .../siacs/conversations/parser/IqParser.java | 186 ++- .../conversations/parser/MessageParser.java | 33 +- .../conversations/parser/PresenceParser.java | 32 +- .../persistance/DatabaseBackend.java | 9 +- .../persistance/UnifiedPushDatabase.java | 39 +- .../conversations/services/AvatarService.java | 1347 +++++++++-------- .../services/CallIntegration.java | 2 +- .../CallIntegrationConnectionService.java | 22 +- .../services/ChannelDiscoveryService.java | 33 +- .../services/NotificationService.java | 14 +- .../services/ShortcutService.java | 17 +- .../services/UnifiedPushBroker.java | 69 +- .../services/XmppConnectionService.java | 19 +- .../conversations/ui/BlockContactDialog.java | 148 +- .../conversations/ui/BlocklistActivity.java | 180 +-- .../ui/ChannelDiscoveryActivity.java | 2 +- ...hooseAccountForProfilePictureActivity.java | 25 +- .../ui/ChooseContactActivity.java | 119 +- .../ui/ConferenceDetailsActivity.java | 9 +- .../ui/ContactDetailsActivity.java | 14 +- .../ui/ConversationFragment.java | 9 +- .../ui/ConversationsOverviewFragment.java | 740 ++++----- .../ui/CreatePublicChannelDialog.java | 169 ++- .../conversations/ui/EditAccountActivity.java | 46 +- .../conversations/ui/EnterJidDialog.java | 35 +- .../ui/MediaBrowserActivity.java | 29 +- .../ui/PublishProfilePictureActivity.java | 6 +- .../conversations/ui/RtpSessionActivity.java | 53 +- .../conversations/ui/ShareWithActivity.java | 2 +- .../ui/StartConversationActivity.java | 50 +- .../conversations/ui/TrustKeysActivity.java | 924 +++++------ .../conversations/ui/UriHandlerActivity.java | 30 +- .../siacs/conversations/ui/XmppActivity.java | 8 +- .../ui/adapter/AccountAdapter.java | 46 +- .../ui/adapter/KnownHostsAdapter.java | 103 +- .../fragment/settings/UpSettingsFragment.java | 48 +- .../conversations/utils/AccountUtils.java | 18 +- .../conversations/utils/BackupFileHeader.java | 38 +- .../conversations/utils/CryptoHelper.java | 99 +- .../utils/IrregularUnicodeDetector.java | 412 ++--- .../siacs/conversations/utils/JidHelper.java | 18 +- .../eu/siacs/conversations/utils/XmppUri.java | 44 +- .../worker/ExportBackupWorker.java | 14 +- .../eu/siacs/conversations/xml/Element.java | 24 +- .../siacs/conversations/xmpp/InvalidJid.java | 155 -- .../java/eu/siacs/conversations/xmpp/Jid.java | 353 ++++- .../siacs/conversations/xmpp/WrappedJid.java | 130 -- .../conversations/xmpp/XmppConnection.java | 6 +- .../xmpp/jingle/JingleConnectionManager.java | 27 +- .../xmpp/jingle/JingleRtpConnection.java | 126 +- .../transports/SocksByteStreamsTransport.java | 22 +- .../android/xmpp/model/bind/Bind.java | 3 +- .../model/sasl2/AuthorizationIdentifier.java | 6 +- .../android/xmpp/model/stanza/Stanza.java | 5 +- 73 files changed, 3852 insertions(+), 3616 deletions(-) delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/InvalidJid.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/WrappedJid.java diff --git a/src/conversations/java/eu/siacs/conversations/entities/AccountConfiguration.java b/src/conversations/java/eu/siacs/conversations/entities/AccountConfiguration.java index 65b6804f9..c3326d8ee 100644 --- a/src/conversations/java/eu/siacs/conversations/entities/AccountConfiguration.java +++ b/src/conversations/java/eu/siacs/conversations/entities/AccountConfiguration.java @@ -5,7 +5,6 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonSyntaxException; import com.google.gson.annotations.SerializedName; - import eu.siacs.conversations.xmpp.Jid; public class AccountConfiguration { @@ -17,7 +16,7 @@ public class AccountConfiguration { public String password; public Jid getJid() { - return Jid.ofEscaped(address); + return Jid.of(address); } public static AccountConfiguration parse(final String input) { @@ -27,24 +26,17 @@ public class AccountConfiguration { } catch (JsonSyntaxException e) { throw new IllegalArgumentException("Not a valid JSON string", e); } - Preconditions.checkArgument( - c.protocol == Protocol.XMPP, - "Protocol must be XMPP" - ); + Preconditions.checkArgument(c.protocol == Protocol.XMPP, "Protocol must be XMPP"); Preconditions.checkArgument( c.address != null && c.getJid().isBareJid() && !c.getJid().isDomainJid(), - "Invalid XMPP address" - ); + "Invalid XMPP address"); Preconditions.checkArgument( - c.password != null && c.password.length() > 0, - "No password specified" - ); + c.password != null && !c.password.isEmpty(), "No password specified"); return c; } public enum Protocol { - @SerializedName("xmpp") XMPP, + @SerializedName("xmpp") + XMPP, } - } - diff --git a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java index dd96468ee..c15208bfa 100644 --- a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java +++ b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java @@ -16,10 +16,8 @@ import android.os.Binder; import android.os.IBinder; import android.provider.OpenableColumns; import android.util.Log; - import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; - import com.google.common.base.Charsets; import com.google.common.base.Stopwatch; import com.google.common.io.CountingInputStream; @@ -27,7 +25,6 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore; @@ -41,14 +38,6 @@ import eu.siacs.conversations.utils.BackupFileHeader; import eu.siacs.conversations.utils.SerialSingleThreadExecutor; import eu.siacs.conversations.worker.ExportBackupWorker; import eu.siacs.conversations.xmpp.Jid; - -import org.bouncycastle.crypto.engines.AESEngine; -import org.bouncycastle.crypto.io.CipherInputStream; -import org.bouncycastle.crypto.modes.AEADBlockCipher; -import org.bouncycastle.crypto.modes.GCMBlockCipher; -import org.bouncycastle.crypto.params.AEADParameters; -import org.bouncycastle.crypto.params.KeyParameter; - import java.io.BufferedReader; import java.io.DataInputStream; import java.io.File; @@ -72,8 +61,13 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; import java.util.zip.ZipException; - import javax.crypto.BadPaddingException; +import org.bouncycastle.crypto.engines.AESEngine; +import org.bouncycastle.crypto.io.CipherInputStream; +import org.bouncycastle.crypto.modes.AEADBlockCipher; +import org.bouncycastle.crypto.modes.GCMBlockCipher; +import org.bouncycastle.crypto.params.AEADParameters; +import org.bouncycastle.crypto.params.KeyParameter; public class ImportBackupService extends Service { @@ -314,10 +308,11 @@ public class ImportBackupService extends Service { final Jid jid = backupFileHeader.getJid(); final Cursor countCursor = db.rawQuery( - "select count(messages.uuid) from messages join conversations on conversations.uuid=messages.conversationUuid join accounts on conversations.accountUuid=accounts.uuid where accounts.username=? and accounts.server=?", - new String[] { - jid.getEscapedLocal(), jid.getDomain().toEscapedString() - }); + "select count(messages.uuid) from messages join conversations on" + + " conversations.uuid=messages.conversationUuid join accounts on" + + " conversations.accountUuid=accounts.uuid where" + + " accounts.username=? and accounts.server=?", + new String[] {jid.getLocal(), jid.getDomain().toString()}); countCursor.moveToFirst(); final int count = countCursor.getInt(0); Log.d( diff --git a/src/conversations/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java b/src/conversations/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java index 9228a5170..874557198 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java @@ -11,13 +11,10 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.databinding.DataBindingUtil; - import com.google.android.material.color.MaterialColors; import com.google.common.base.Strings; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityEasyInviteBinding; @@ -153,7 +150,7 @@ public class EasyOnboardingInviteActivity extends XmppActivity } final Intent launchIntent = getIntent(); final String accountExtra = launchIntent.getStringExtra(EXTRA_ACCOUNT); - final Jid jid = accountExtra == null ? null : Jid.ofEscaped(accountExtra); + final Jid jid = accountExtra == null ? null : Jid.of(accountExtra); if (jid == null) { return; } @@ -163,7 +160,7 @@ public class EasyOnboardingInviteActivity extends XmppActivity public static void launch(final Account account, final Activity context) { final Intent intent = new Intent(context, EasyOnboardingInviteActivity.class); - intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toString()); context.startActivity(intent); } diff --git a/src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java b/src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java index b6d4d452e..bebf9bab6 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java @@ -7,9 +7,7 @@ import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.widget.Toast; - import androidx.databinding.DataBindingUtil; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityMagicCreateBinding; @@ -17,7 +15,6 @@ import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.InstallReferrerUtils; import eu.siacs.conversations.xmpp.Jid; - import java.security.SecureRandom; public class MagicCreateActivity extends XmppActivity implements TextWatcher { @@ -68,15 +65,15 @@ public class MagicCreateActivity extends XmppActivity implements TextWatcher { final boolean fixedUsername; if (this.domain != null && this.username != null) { fixedUsername = true; - jid = Jid.ofLocalAndDomainEscaped(this.username, this.domain); + jid = Jid.ofLocalAndDomain(this.username, this.domain); } else if (this.domain != null) { fixedUsername = false; - jid = Jid.ofLocalAndDomainEscaped(username, this.domain); + jid = Jid.ofLocalAndDomain(username, this.domain); } else { fixedUsername = false; - jid = Jid.ofLocalAndDomainEscaped(username, Config.MAGIC_CREATE_DOMAIN); + jid = Jid.ofLocalAndDomain(username, Config.MAGIC_CREATE_DOMAIN); } - if (!jid.getEscapedLocal().equals(jid.getLocal()) + if (!jid.getLocal().equals(jid.getLocal()) || (this.username == null && username.length() < 3)) { binding.usernameLayout.setError(getString(R.string.invalid_username)); binding.username.requestFocus(); @@ -146,12 +143,11 @@ public class MagicCreateActivity extends XmppActivity implements TextWatcher { binding.fullJid.setVisibility(View.VISIBLE); final Jid jid; if (this.domain == null) { - jid = Jid.ofLocalAndDomainEscaped(username, Config.MAGIC_CREATE_DOMAIN); + jid = Jid.ofLocalAndDomain(username, Config.MAGIC_CREATE_DOMAIN); } else { - jid = Jid.ofLocalAndDomainEscaped(username, this.domain); + jid = Jid.ofLocalAndDomain(username, this.domain); } - binding.fullJid.setText( - getString(R.string.your_full_jid_will_be, jid.toEscapedString())); + binding.fullJid.setText(getString(R.string.your_full_jid_will_be, jid.toString())); binding.usernameLayout.setError(null); } catch (final IllegalArgumentException e) { binding.fullJid.setVisibility(View.INVISIBLE); diff --git a/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java index 6ab32682f..c2a82f8d5 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java @@ -17,13 +17,10 @@ import android.view.MenuItem; import android.view.View; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.databinding.DataBindingUtil; - import com.google.common.base.Strings; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityManageAccountsBinding; @@ -34,12 +31,10 @@ import eu.siacs.conversations.ui.adapter.AccountAdapter; import eu.siacs.conversations.ui.util.MenuDoubleTabUtil; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.XmppConnection; - -import org.openintents.openpgp.util.OpenPgpApi; - import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import org.openintents.openpgp.util.OpenPgpApi; public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, @@ -95,7 +90,7 @@ public class ManageAccountActivity extends XmppActivity String jid = savedInstanceState.getString(STATE_SELECTED_ACCOUNT); if (jid != null) { try { - this.selectedAccountJid = Jid.ofEscaped(jid); + this.selectedAccountJid = Jid.of(jid); } catch (IllegalArgumentException e) { this.selectedAccountJid = null; } @@ -113,7 +108,7 @@ public class ManageAccountActivity extends XmppActivity public void onSaveInstanceState(@NonNull final Bundle savedInstanceState) { if (selectedAccount != null) { savedInstanceState.putString( - STATE_SELECTED_ACCOUNT, selectedAccount.getJid().asBareJid().toEscapedString()); + STATE_SELECTED_ACCOUNT, selectedAccount.getJid().asBareJid().toString()); } super.onSaveInstanceState(savedInstanceState); } @@ -132,7 +127,7 @@ public class ManageAccountActivity extends XmppActivity menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(false); menu.findItem(R.id.mgmt_account_publish_avatar).setVisible(false); } - menu.setHeaderTitle(this.selectedAccount.getJid().asBareJid().toEscapedString()); + menu.setHeaderTitle(this.selectedAccount.getJid().asBareJid().toString()); } @Override @@ -297,7 +292,7 @@ public class ManageAccountActivity extends XmppActivity private void publishAvatar(Account account) { Intent intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class); - intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toString()); startActivity(intent); } @@ -357,7 +352,8 @@ public class ManageAccountActivity extends XmppActivity Log.d( Config.LOGTAG, account.getJid().asBareJid() - + ": quick start disabled. account will regain this capability on the next connect"); + + ": quick start disabled. account will regain this capability on the" + + " next connect"); } if (!xmppConnectionService.updateAccount(account)) { Toast.makeText(this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show(); diff --git a/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java b/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java index b2a40976c..514d0ccfd 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java @@ -1,5 +1,8 @@ package eu.siacs.conversations.ui; +import static eu.siacs.conversations.utils.PermissionUtils.allGranted; +import static eu.siacs.conversations.utils.PermissionUtils.writeGranted; + import android.Manifest; import android.content.ActivityNotFoundException; import android.content.Intent; @@ -12,14 +15,10 @@ import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.databinding.DataBindingUtil; - -import java.util.Arrays; -import java.util.List; - +import com.google.common.base.Strings; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityWelcomeBinding; @@ -30,11 +29,8 @@ import eu.siacs.conversations.utils.InstallReferrerUtils; import eu.siacs.conversations.utils.SignupUtils; import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.Jid; - -import static eu.siacs.conversations.utils.PermissionUtils.allGranted; -import static eu.siacs.conversations.utils.PermissionUtils.writeGranted; - -import com.google.common.base.Strings; +import java.util.Arrays; +import java.util.List; public class WelcomeActivity extends XmppActivity implements XmppConnectionService.OnAccountCreated, KeyChainAliasCallback { @@ -196,7 +192,7 @@ public class WelcomeActivity extends XmppActivity @Override public void onAccountCreated(final Account account) { final Intent intent = new Intent(this, EditAccountActivity.class); - intent.putExtra("jid", account.getJid().asBareJid().toEscapedString()); + intent.putExtra("jid", account.getJid().asBareJid().toString()); intent.putExtra("init", true); addInviteUri(intent); startActivity(intent); diff --git a/src/conversations/java/eu/siacs/conversations/utils/ProvisioningUtils.java b/src/conversations/java/eu/siacs/conversations/utils/ProvisioningUtils.java index 593291d95..fdfe28dbb 100644 --- a/src/conversations/java/eu/siacs/conversations/utils/ProvisioningUtils.java +++ b/src/conversations/java/eu/siacs/conversations/utils/ProvisioningUtils.java @@ -3,15 +3,13 @@ package eu.siacs.conversations.utils; import android.app.Activity; import android.content.Intent; import android.widget.Toast; - -import java.util.List; - import eu.siacs.conversations.R; import eu.siacs.conversations.entities.AccountConfiguration; import eu.siacs.conversations.persistance.DatabaseBackend; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.EditAccountActivity; import eu.siacs.conversations.xmpp.Jid; +import java.util.List; public class ProvisioningUtils { @@ -20,7 +18,8 @@ public class ProvisioningUtils { try { accountConfiguration = AccountConfiguration.parse(json); } catch (final IllegalArgumentException e) { - Toast.makeText(activity, R.string.improperly_formatted_provisioning, Toast.LENGTH_LONG).show(); + Toast.makeText(activity, R.string.improperly_formatted_provisioning, Toast.LENGTH_LONG) + .show(); return; } final Jid jid = accountConfiguration.getJid(); @@ -31,13 +30,12 @@ public class ProvisioningUtils { } final Intent serviceIntent = new Intent(activity, XmppConnectionService.class); serviceIntent.setAction(XmppConnectionService.ACTION_PROVISION_ACCOUNT); - serviceIntent.putExtra("address", jid.asBareJid().toEscapedString()); + serviceIntent.putExtra("address", jid.asBareJid().toString()); serviceIntent.putExtra("password", accountConfiguration.password); Compatibility.startService(activity, serviceIntent); final Intent intent = new Intent(activity, EditAccountActivity.class); - intent.putExtra("jid", jid.asBareJid().toEscapedString()); + intent.putExtra("jid", jid.asBareJid().toString()); intent.putExtra("init", true); activity.startActivity(intent); } - } diff --git a/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java b/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java index fb088234a..b8123947f 100644 --- a/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java +++ b/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java @@ -2,7 +2,6 @@ package eu.siacs.conversations.utils; import android.app.Activity; import android.content.Intent; - import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.XmppConnectionService; @@ -21,13 +20,14 @@ public class SignupUtils { return true; } - public static Intent getTokenRegistrationIntent(final Activity activity, Jid jid, String preAuth) { + public static Intent getTokenRegistrationIntent( + final Activity activity, Jid jid, String preAuth) { final Intent intent = new Intent(activity, MagicCreateActivity.class); if (jid.isDomainJid()) { - intent.putExtra(MagicCreateActivity.EXTRA_DOMAIN, jid.getDomain().toEscapedString()); + intent.putExtra(MagicCreateActivity.EXTRA_DOMAIN, jid.getDomain().toString()); } else { - intent.putExtra(MagicCreateActivity.EXTRA_DOMAIN, jid.getDomain().toEscapedString()); - intent.putExtra(MagicCreateActivity.EXTRA_USERNAME, jid.getEscapedLocal()); + intent.putExtra(MagicCreateActivity.EXTRA_DOMAIN, jid.getDomain().toString()); + intent.putExtra(MagicCreateActivity.EXTRA_USERNAME, jid.getLocal()); } intent.putExtra(MagicCreateActivity.EXTRA_PRE_AUTH, preAuth); return intent; @@ -55,7 +55,9 @@ public class SignupUtils { intent = new Intent(activity, EditAccountActivity.class); intent.putExtra("jid", pendingAccount.getJid().asBareJid().toString()); if (!pendingAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) { - intent.putExtra(EditAccountActivity.EXTRA_FORCE_REGISTER, pendingAccount.isOptionSet(Account.OPTION_REGISTER)); + intent.putExtra( + EditAccountActivity.EXTRA_FORCE_REGISTER, + pendingAccount.isOptionSet(Account.OPTION_REGISTER)); } } else { if (service.getAccounts().size() == 0) { @@ -74,4 +76,4 @@ public class SignupUtils { intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); return intent; } -} \ No newline at end of file +} diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/External.java b/src/main/java/eu/siacs/conversations/crypto/sasl/External.java index 6aba413a5..2e8adf189 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/External.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/External.java @@ -1,10 +1,8 @@ package eu.siacs.conversations.crypto.sasl; import android.util.Base64; - -import javax.net.ssl.SSLSocket; - import eu.siacs.conversations.entities.Account; +import javax.net.ssl.SSLSocket; public class External extends SaslMechanism { @@ -27,6 +25,6 @@ public class External extends SaslMechanism { @Override public String getClientFirstMessage(final SSLSocket sslSocket) { return Base64.encodeToString( - account.getJid().asBareJid().toEscapedString().getBytes(), Base64.NO_WRAP); + account.getJid().asBareJid().toString().getBytes(), Base64.NO_WRAP); } } diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java index ce13ba9f3..f55618fe2 100644 --- a/src/main/java/eu/siacs/conversations/entities/Account.java +++ b/src/main/java/eu/siacs/conversations/entities/Account.java @@ -268,7 +268,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable } public String getUsername() { - return jid.getEscapedLocal(); + return jid.getLocal(); } public boolean setJid(final Jid next) { @@ -292,7 +292,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable } public String getServer() { - return jid.getDomain().toEscapedString(); + return jid.getDomain().toString(); } public String getPassword() { @@ -508,7 +508,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable final ContentValues values = new ContentValues(); values.put(UUID, uuid); values.put(USERNAME, jid.getLocal()); - values.put(SERVER, jid.getDomain().toEscapedString()); + values.put(SERVER, jid.getDomain().toString()); values.put(PASSWORD, password); values.put(OPTIONS, options); synchronized (this.keys) { @@ -698,11 +698,11 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable public String getShareableUri() { List<XmppUri.Fingerprint> fingerprints = this.getFingerprints(); - String uri = "xmpp:" + this.getJid().asBareJid().toEscapedString(); - if (fingerprints.size() > 0) { - return XmppUri.getFingerprintUri(uri, fingerprints, ';'); - } else { + final String uri = "xmpp:" + this.getJid().asBareJid().toString(); + if (fingerprints.isEmpty()) { return uri; + } else { + return XmppUri.getFingerprintUri(uri, fingerprints, ';'); } } @@ -710,11 +710,11 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable List<XmppUri.Fingerprint> fingerprints = this.getFingerprints(); String uri = "https://conversations.im/i/" - + XmppUri.lameUrlEncode(this.getJid().asBareJid().toEscapedString()); - if (fingerprints.size() > 0) { - return XmppUri.getFingerprintUri(uri, fingerprints, '&'); - } else { + + XmppUri.lameUrlEncode(this.getJid().asBareJid().toString()); + if (fingerprints.isEmpty()) { return uri; + } else { + return XmppUri.getFingerprintUri(uri, fingerprints, '&'); } } diff --git a/src/main/java/eu/siacs/conversations/entities/Bookmark.java b/src/main/java/eu/siacs/conversations/entities/Bookmark.java index de7694717..2a0094828 100644 --- a/src/main/java/eu/siacs/conversations/entities/Bookmark.java +++ b/src/main/java/eu/siacs/conversations/entities/Bookmark.java @@ -1,13 +1,15 @@ package eu.siacs.conversations.entities; import android.content.Context; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; - +import eu.siacs.conversations.utils.StringUtils; +import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xml.Namespace; +import eu.siacs.conversations.xmpp.Jid; import java.lang.ref.WeakReference; import java.util.Collections; import java.util.HashMap; @@ -15,265 +17,260 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import eu.siacs.conversations.utils.StringUtils; -import eu.siacs.conversations.utils.UIHelper; -import eu.siacs.conversations.xml.Element; -import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.InvalidJid; -import eu.siacs.conversations.xmpp.Jid; - public class Bookmark extends Element implements ListItem { - private final Account account; - private WeakReference<Conversation> conversation; - private Jid jid; - protected Element extensions = new Element("extensions", Namespace.BOOKMARKS2); - - public Bookmark(final Account account, final Jid jid) { - super("conference"); - this.jid = jid; - this.setAttribute("jid", jid); - this.account = account; - } - - private Bookmark(Account account) { - super("conference"); - this.account = account; - } - - public static Map<Jid, Bookmark> parseFromStorage(Element storage, Account account) { - if (storage == null) { - return Collections.emptyMap(); - } - final HashMap<Jid, Bookmark> bookmarks = new HashMap<>(); - for (final Element item : storage.getChildren()) { - if (item.getName().equals("conference")) { - final Bookmark bookmark = Bookmark.parse(item, account); - if (bookmark != null) { - final Bookmark old = bookmarks.put(bookmark.jid, bookmark); - if (old != null && old.getBookmarkName() != null && bookmark.getBookmarkName() == null) { - bookmark.setBookmarkName(old.getBookmarkName()); - } - } - } - } - return bookmarks; - } - - public static Map<Jid, Bookmark> parseFromPubSub(final Element pubSub, final Account account) { - if (pubSub == null) { - return Collections.emptyMap(); - } - final Element items = pubSub.findChild("items"); - if (items != null && Namespace.BOOKMARKS2.equals(items.getAttribute("node"))) { - final Map<Jid, Bookmark> bookmarks = new HashMap<>(); - for(Element item : items.getChildren()) { - if (item.getName().equals("item")) { - final Bookmark bookmark = Bookmark.parseFromItem(item, account); - if (bookmark != null) { - bookmarks.put(bookmark.jid, bookmark); - } - } - } - return bookmarks; - } - return Collections.emptyMap(); - } - - public static Bookmark parse(Element element, Account account) { - Bookmark bookmark = new Bookmark(account); - bookmark.setAttributes(element.getAttributes()); - bookmark.setChildren(element.getChildren()); - bookmark.jid = InvalidJid.getNullForInvalid(bookmark.getAttributeAsJid("jid")); - if (bookmark.jid == null) { - return null; - } - return bookmark; - } - - public static Bookmark parseFromItem(Element item, Account account) { - final Element conference = item.findChild("conference", Namespace.BOOKMARKS2); - if (conference == null) { - return null; - } - final Bookmark bookmark = new Bookmark(account); - bookmark.jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("id")); - // TODO verify that we only use bare jids and ignore full jids - if (bookmark.jid == null) { - return null; - } - bookmark.setBookmarkName(conference.getAttribute("name")); - bookmark.setAutojoin(conference.getAttributeAsBoolean("autojoin")); - bookmark.setNick(conference.findChildContent("nick")); - bookmark.setPassword(conference.findChildContent("password")); - final Element extensions = conference.findChild("extensions", Namespace.BOOKMARKS2); - if (extensions != null) { - bookmark.extensions = extensions; - } - return bookmark; - } - - public Element getExtensions() { - return extensions; - } - - public void setAutojoin(boolean autojoin) { - if (autojoin) { - this.setAttribute("autojoin", "true"); - } else { - this.setAttribute("autojoin", "false"); - } - } - - @Override - public int compareTo(final @NonNull ListItem another) { - return this.getDisplayName().compareToIgnoreCase( - another.getDisplayName()); - } - - @Override - public String getDisplayName() { - final Conversation c = getConversation(); - final String name = getBookmarkName(); - if (c != null) { - return c.getName().toString(); - } else if (printableValue(name, false)) { - return name.trim(); - } else { - Jid jid = this.getJid(); - return jid != null && jid.getLocal() != null ? jid.getLocal() : ""; - } - } - - public static boolean printableValue(@Nullable String value, boolean permitNone) { - return value != null && !value.trim().isEmpty() && (permitNone || !"None".equals(value)); - } - - public static boolean printableValue(@Nullable String value) { - return printableValue(value, true); - } - - @Override - public Jid getJid() { - return this.jid; - } - - public Jid getFullJid() { - final String nick = Strings.nullToEmpty(getNick()).trim(); - if (jid == null || nick.isEmpty()) { - return jid; - } - try { - return jid.withResource(nick); - } catch (final IllegalArgumentException e) { - return jid; - } - } - - @Override - public List<Tag> getTags(final Context context) { - final ImmutableList.Builder<Tag> tags = new ImmutableList.Builder<>(); - for (final Element element : getChildren()) { - final String content = element.getContent(); - if (Strings.isNullOrEmpty(content)) { - continue; - } - if (element.getName().equals("group")) { - tags.add(new Tag(content)); - } - } - return tags.build(); - } - - public String getNick() { - return Strings.emptyToNull(this.findChildContent("nick")); - } - - public void setNick(String nick) { - Element element = this.findChild("nick"); - if (element == null) { - element = this.addChild("nick"); - } - element.setContent(nick); - } - - public boolean autojoin() { - return this.getAttributeAsBoolean("autojoin"); - } - - public String getPassword() { - return this.findChildContent("password"); - } - - public void setPassword(String password) { - Element element = this.findChild("password"); - if (element != null) { - element.setContent(password); - } - } - - @Override - public boolean match(Context context, String needle) { - if (needle == null) { - return true; - } - needle = needle.toLowerCase(Locale.US); - final Jid jid = getJid(); - return (jid != null && jid.toString().contains(needle)) || - getDisplayName().toLowerCase(Locale.US).contains(needle) || - matchInTag(context, needle); - } - - private boolean matchInTag(Context context, String needle) { - needle = needle.toLowerCase(Locale.US); - for (Tag tag : getTags(context)) { - if (tag.getName().toLowerCase(Locale.US).contains(needle)) { - return true; - } - } - return false; - } - - public Account getAccount() { - return this.account; - } - - public synchronized Conversation getConversation() { - return this.conversation != null ? this.conversation.get() : null; - } - - public synchronized void setConversation(Conversation conversation) { - if (this.conversation != null) { - this.conversation.clear(); - } - if (conversation == null) { - this.conversation = null; - } else { - this.conversation = new WeakReference<>(conversation); - } - } - - public String getBookmarkName() { - return this.getAttribute("name"); - } - - public boolean setBookmarkName(String name) { - String before = getBookmarkName(); - if (name != null) { - this.setAttribute("name", name); - } else { - this.removeAttribute("name"); - } - return StringUtils.changed(before, name); - } - - @Override - public int getAvatarBackgroundColor() { - return UIHelper.getColorForName(jid != null ? jid.asBareJid().toString() : getDisplayName()); - } - - @Override - public String getAvatarName() { - return getDisplayName(); - } + private final Account account; + private WeakReference<Conversation> conversation; + private Jid jid; + protected Element extensions = new Element("extensions", Namespace.BOOKMARKS2); + + public Bookmark(final Account account, final Jid jid) { + super("conference"); + this.jid = jid; + this.setAttribute("jid", jid); + this.account = account; + } + + private Bookmark(Account account) { + super("conference"); + this.account = account; + } + + public static Map<Jid, Bookmark> parseFromStorage(Element storage, Account account) { + if (storage == null) { + return Collections.emptyMap(); + } + final HashMap<Jid, Bookmark> bookmarks = new HashMap<>(); + for (final Element item : storage.getChildren()) { + if (item.getName().equals("conference")) { + final Bookmark bookmark = Bookmark.parse(item, account); + if (bookmark != null) { + final Bookmark old = bookmarks.put(bookmark.jid, bookmark); + if (old != null + && old.getBookmarkName() != null + && bookmark.getBookmarkName() == null) { + bookmark.setBookmarkName(old.getBookmarkName()); + } + } + } + } + return bookmarks; + } + + public static Map<Jid, Bookmark> parseFromPubSub(final Element pubSub, final Account account) { + if (pubSub == null) { + return Collections.emptyMap(); + } + final Element items = pubSub.findChild("items"); + if (items != null && Namespace.BOOKMARKS2.equals(items.getAttribute("node"))) { + final Map<Jid, Bookmark> bookmarks = new HashMap<>(); + for (Element item : items.getChildren()) { + if (item.getName().equals("item")) { + final Bookmark bookmark = Bookmark.parseFromItem(item, account); + if (bookmark != null) { + bookmarks.put(bookmark.jid, bookmark); + } + } + } + return bookmarks; + } + return Collections.emptyMap(); + } + + public static Bookmark parse(Element element, Account account) { + Bookmark bookmark = new Bookmark(account); + bookmark.setAttributes(element.getAttributes()); + bookmark.setChildren(element.getChildren()); + bookmark.jid = Jid.Invalid.getNullForInvalid(bookmark.getAttributeAsJid("jid")); + if (bookmark.jid == null) { + return null; + } + return bookmark; + } + + public static Bookmark parseFromItem(Element item, Account account) { + final Element conference = item.findChild("conference", Namespace.BOOKMARKS2); + if (conference == null) { + return null; + } + final Bookmark bookmark = new Bookmark(account); + bookmark.jid = Jid.Invalid.getNullForInvalid(item.getAttributeAsJid("id")); + // TODO verify that we only use bare jids and ignore full jids + if (bookmark.jid == null) { + return null; + } + bookmark.setBookmarkName(conference.getAttribute("name")); + bookmark.setAutojoin(conference.getAttributeAsBoolean("autojoin")); + bookmark.setNick(conference.findChildContent("nick")); + bookmark.setPassword(conference.findChildContent("password")); + final Element extensions = conference.findChild("extensions", Namespace.BOOKMARKS2); + if (extensions != null) { + bookmark.extensions = extensions; + } + return bookmark; + } + + public Element getExtensions() { + return extensions; + } + + public void setAutojoin(boolean autojoin) { + if (autojoin) { + this.setAttribute("autojoin", "true"); + } else { + this.setAttribute("autojoin", "false"); + } + } + + @Override + public int compareTo(final @NonNull ListItem another) { + return this.getDisplayName().compareToIgnoreCase(another.getDisplayName()); + } + + @Override + public String getDisplayName() { + final Conversation c = getConversation(); + final String name = getBookmarkName(); + if (c != null) { + return c.getName().toString(); + } else if (printableValue(name, false)) { + return name.trim(); + } else { + Jid jid = this.getJid(); + return jid != null && jid.getLocal() != null ? jid.getLocal() : ""; + } + } + + public static boolean printableValue(@Nullable String value, boolean permitNone) { + return value != null && !value.trim().isEmpty() && (permitNone || !"None".equals(value)); + } + + public static boolean printableValue(@Nullable String value) { + return printableValue(value, true); + } + + @Override + public Jid getJid() { + return this.jid; + } + + public Jid getFullJid() { + final String nick = Strings.nullToEmpty(getNick()).trim(); + if (jid == null || nick.isEmpty()) { + return jid; + } + try { + return jid.withResource(nick); + } catch (final IllegalArgumentException e) { + return jid; + } + } + + @Override + public List<Tag> getTags(final Context context) { + final ImmutableList.Builder<Tag> tags = new ImmutableList.Builder<>(); + for (final Element element : getChildren()) { + final String content = element.getContent(); + if (Strings.isNullOrEmpty(content)) { + continue; + } + if (element.getName().equals("group")) { + tags.add(new Tag(content)); + } + } + return tags.build(); + } + + public String getNick() { + return Strings.emptyToNull(this.findChildContent("nick")); + } + + public void setNick(String nick) { + Element element = this.findChild("nick"); + if (element == null) { + element = this.addChild("nick"); + } + element.setContent(nick); + } + + public boolean autojoin() { + return this.getAttributeAsBoolean("autojoin"); + } + + public String getPassword() { + return this.findChildContent("password"); + } + + public void setPassword(String password) { + Element element = this.findChild("password"); + if (element != null) { + element.setContent(password); + } + } + + @Override + public boolean match(Context context, String needle) { + if (needle == null) { + return true; + } + needle = needle.toLowerCase(Locale.US); + final Jid jid = getJid(); + return (jid != null && jid.toString().contains(needle)) + || getDisplayName().toLowerCase(Locale.US).contains(needle) + || matchInTag(context, needle); + } + + private boolean matchInTag(Context context, String needle) { + needle = needle.toLowerCase(Locale.US); + for (Tag tag : getTags(context)) { + if (tag.getName().toLowerCase(Locale.US).contains(needle)) { + return true; + } + } + return false; + } + + public Account getAccount() { + return this.account; + } + + public synchronized Conversation getConversation() { + return this.conversation != null ? this.conversation.get() : null; + } + + public synchronized void setConversation(Conversation conversation) { + if (this.conversation != null) { + this.conversation.clear(); + } + if (conversation == null) { + this.conversation = null; + } else { + this.conversation = new WeakReference<>(conversation); + } + } + + public String getBookmarkName() { + return this.getAttribute("name"); + } + + public boolean setBookmarkName(String name) { + String before = getBookmarkName(); + if (name != null) { + this.setAttribute("name", name); + } else { + this.removeAttribute("name"); + } + return StringUtils.changed(before, name); + } + + @Override + public int getAvatarBackgroundColor() { + return UIHelper.getColorForName( + jid != null ? jid.asBareJid().toString() : getDisplayName()); + } + + @Override + public String getAvatarName() { + return getDisplayName(); + } } diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java index 6437cffa3..b15381e58 100644 --- a/src/main/java/eu/siacs/conversations/entities/Contact.java +++ b/src/main/java/eu/siacs/conversations/entities/Contact.java @@ -5,22 +5,8 @@ import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.text.TextUtils; - import androidx.annotation.NonNull; - import com.google.common.base.Strings; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Objects; - import eu.siacs.conversations.Config; import eu.siacs.conversations.android.AbstractPhoneContact; import eu.siacs.conversations.android.JabberIdContact; @@ -31,6 +17,15 @@ import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.jingle.RtpCapability; import eu.siacs.conversations.xmpp.pep.Avatar; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; public class Contact implements ListItem, Blockable { public static final String TABLENAME = "contacts"; @@ -69,10 +64,21 @@ public class Contact implements ListItem, Blockable { private String mLastPresence = null; private RtpCapability.Capability rtpCapability; - public Contact(final String account, final String systemName, final String serverName, final String presenceName, - final Jid jid, final int subscription, final String photoUri, - final Uri systemAccount, final String keys, final String avatar, final long lastseen, - final String presence, final String groups, final RtpCapability.Capability rtpCapability) { + public Contact( + final String account, + final String systemName, + final String serverName, + final String presenceName, + final Jid jid, + final int subscription, + final String photoUri, + final Uri systemAccount, + final String keys, + final String avatar, + final long lastseen, + final String presence, + final String groups, + final RtpCapability.Capability rtpCapability) { this.accountUuid = account; this.systemName = systemName; this.serverName = serverName; @@ -91,7 +97,7 @@ public class Contact implements ListItem, Blockable { if (avatar != null) { this.avatar = new Avatar(); this.avatar.sha1sum = avatar; - this.avatar.origin = Avatar.Origin.VCARD; //always assume worst + this.avatar.origin = Avatar.Origin.VCARD; // always assume worst } try { this.groups = (groups == null ? new JSONArray() : new JSONArray(groups)); @@ -122,7 +128,8 @@ public class Contact implements ListItem, Blockable { } catch (Exception e) { systemAccount = null; } - return new Contact(cursor.getString(cursor.getColumnIndex(ACCOUNT)), + return new Contact( + cursor.getString(cursor.getColumnIndex(ACCOUNT)), cursor.getString(cursor.getColumnIndex(SYSTEMNAME)), cursor.getString(cursor.getColumnIndex(SERVERNAME)), cursor.getString(cursor.getColumnIndex(PRESENCE_NAME)), @@ -135,7 +142,8 @@ public class Contact implements ListItem, Blockable { cursor.getLong(cursor.getColumnIndex(LAST_TIME)), cursor.getString(cursor.getColumnIndex(LAST_PRESENCE)), cursor.getString(cursor.getColumnIndex(GROUPS)), - RtpCapability.Capability.of(cursor.getString(cursor.getColumnIndex(RTP_CAPABILITY)))); + RtpCapability.Capability.of( + cursor.getString(cursor.getColumnIndex(RTP_CAPABILITY)))); } public String getDisplayName() { @@ -151,12 +159,15 @@ public class Contact implements ListItem, Blockable { return this.systemName; } else if (!TextUtils.isEmpty(this.serverName)) { return this.serverName; - } else if (!TextUtils.isEmpty(this.presenceName) && ((QuickConversationsService.isQuicksy() && JidHelper.isQuicksyDomain(jid.getDomain())) || mutualPresenceSubscription())) { + } else if (!TextUtils.isEmpty(this.presenceName) + && ((QuickConversationsService.isQuicksy() + && JidHelper.isQuicksyDomain(jid.getDomain())) + || mutualPresenceSubscription())) { return this.presenceName; } else if (jid.getLocal() != null) { return JidHelper.localPartOrFallback(jid); } else { - return jid.getDomain().toEscapedString(); + return jid.getDomain().toString(); } } @@ -166,7 +177,7 @@ public class Contact implements ListItem, Blockable { } else if (jid.getLocal() != null) { return JidHelper.localPartOrFallback(jid); } else { - return jid.getDomain().toEscapedString(); + return jid.getDomain().toString(); } } @@ -201,9 +212,9 @@ public class Contact implements ListItem, Blockable { } return true; } else { - return jid.toString().contains(needle) || - getDisplayName().toLowerCase(Locale.US).contains(needle) || - matchInTag(context, needle); + return jid.toString().contains(needle) + || getDisplayName().toLowerCase(Locale.US).contains(needle) + || matchInTag(context, needle); } } @@ -354,8 +365,8 @@ public class Contact implements ListItem, Blockable { } public boolean showInRoster() { - return (this.getOption(Contact.Options.IN_ROSTER) && (!this - .getOption(Contact.Options.DIRTY_DELETE))) + return (this.getOption(Contact.Options.IN_ROSTER) + && (!this.getOption(Contact.Options.DIRTY_DELETE))) || (this.getOption(Contact.Options.DIRTY_PUSH)); } @@ -430,12 +441,11 @@ public class Contact implements ListItem, Blockable { @Override public int compareTo(@NonNull final ListItem another) { - return this.getDisplayName().compareToIgnoreCase( - another.getDisplayName()); + return this.getDisplayName().compareToIgnoreCase(another.getDisplayName()); } public String getServer() { - return getJid().getDomain().toEscapedString(); + return getJid().getDomain().toString(); } public void setAvatar(Avatar avatar) { @@ -446,7 +456,10 @@ public class Contact implements ListItem, Blockable { if (this.avatar != null && this.avatar.equals(avatar)) { return; } - if (!previouslyOmittedPepFetch && this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) { + if (!previouslyOmittedPepFetch + && this.avatar != null + && this.avatar.origin == Avatar.Origin.PEP + && avatar.origin == Avatar.Origin.VCARD) { return; } this.avatar = avatar; @@ -561,7 +574,8 @@ public class Contact implements ListItem, Blockable { @Override public int getAvatarBackgroundColor() { - return UIHelper.getColorForName(jid != null ? jid.asBareJid().toString() : getDisplayName()); + return UIHelper.getColorForName( + jid != null ? jid.asBareJid().toString() : getDisplayName()); } @Override diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 1f192d57a..6394ee1a4 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -137,8 +137,7 @@ public class Conversation extends AbstractEntity cursor.getString(cursor.getColumnIndexOrThrow(NAME)), cursor.getString(cursor.getColumnIndexOrThrow(CONTACT)), cursor.getString(cursor.getColumnIndexOrThrow(ACCOUNT)), - JidHelper.parseOrFallbackToInvalid( - cursor.getString(cursor.getColumnIndexOrThrow(CONTACTJID))), + Jid.ofOrInvalid(cursor.getString(cursor.getColumnIndexOrThrow(CONTACTJID))), cursor.getLong(cursor.getColumnIndexOrThrow(CREATED)), cursor.getInt(cursor.getColumnIndexOrThrow(STATUS)), cursor.getInt(cursor.getColumnIndexOrThrow(MODE)), diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index 5129d0bfd..a1edc2a03 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -1,14 +1,10 @@ package eu.siacs.conversations.entities; -import android.text.TextUtils; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.services.AvatarService; @@ -21,7 +17,6 @@ import eu.siacs.conversations.xmpp.chatstate.ChatState; import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.forms.Field; import eu.siacs.conversations.xmpp.pep.Avatar; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -68,7 +63,8 @@ public class MucOptions { public boolean setSelf(User user) { this.self = user; final boolean roleChanged = this.conversation.setAttribute("role", user.role.toString()); - final boolean affiliationChanged = this.conversation.setAttribute("affiliation", user.affiliation.toString()); + final boolean affiliationChanged = + this.conversation.setAttribute("affiliation", user.affiliation.toString()); return roleChanged || affiliationChanged; } @@ -123,21 +119,30 @@ public class MucOptions { final var identities = serviceDiscoveryResult.getIdentities(); final String identityName = !identities.isEmpty() ? identities.get(0).getName() : null; final Jid jid = conversation.getJid(); - if (identityName != null && !identityName.equals(jid == null ? null : jid.getEscapedLocal())) { + if (identityName != null && !identityName.equals(jid == null ? null : jid.getLocal())) { name = identityName; } else { name = null; } } boolean changed = conversation.setAttribute("muc_name", name); - changed |= conversation.setAttribute(Conversation.ATTRIBUTE_MEMBERS_ONLY, this.hasFeature("muc_membersonly")); - changed |= conversation.setAttribute(Conversation.ATTRIBUTE_MODERATED, this.hasFeature("muc_moderated")); - changed |= conversation.setAttribute(Conversation.ATTRIBUTE_NON_ANONYMOUS, this.hasFeature("muc_nonanonymous")); + changed |= + conversation.setAttribute( + Conversation.ATTRIBUTE_MEMBERS_ONLY, this.hasFeature("muc_membersonly")); + changed |= + conversation.setAttribute( + Conversation.ATTRIBUTE_MODERATED, this.hasFeature("muc_moderated")); + changed |= + conversation.setAttribute( + Conversation.ATTRIBUTE_NON_ANONYMOUS, this.hasFeature("muc_nonanonymous")); return changed; } private Data getRoomInfoForm() { - final List<Data> forms = serviceDiscoveryResult == null ? Collections.emptyList() : serviceDiscoveryResult.forms; + final List<Data> forms = + serviceDiscoveryResult == null + ? Collections.emptyList() + : serviceDiscoveryResult.forms; return forms.isEmpty() ? new Data() : forms.get(0); } @@ -146,7 +151,8 @@ public class MucOptions { } public boolean hasFeature(String feature) { - return this.serviceDiscoveryResult != null && this.serviceDiscoveryResult.features.contains(feature); + return this.serviceDiscoveryResult != null + && this.serviceDiscoveryResult.features.contains(feature); } public boolean hasVCards() { @@ -154,7 +160,8 @@ public class MucOptions { } public boolean canInvite() { - final boolean hasPermission = !membersOnly() || self.getRole().ranks(Role.MODERATOR) || allowInvites(); + final boolean hasPermission = + !membersOnly() || self.getRole().ranks(Role.MODERATOR) || allowInvites(); return hasPermission && online(); } @@ -177,7 +184,7 @@ public class MucOptions { public boolean allowPm() { final Field field = getRoomInfoForm().getFieldByName("muc#roomconfig_allowpm"); if (field == null) { - return true; //fall back if field does not exists + return true; // fall back if field does not exists } if ("anyone".equals(field.getValue())) { return true; @@ -192,7 +199,7 @@ public class MucOptions { public boolean allowPmRaw() { final Field field = getRoomInfoForm().getFieldByName("muc#roomconfig_allowpm"); - return field == null || Arrays.asList("anyone","participants").contains(field.getValue()); + return field == null || Arrays.asList("anyone", "participants").contains(field.getValue()); } public boolean participating() { @@ -204,7 +211,9 @@ public class MucOptions { } public List<String> getFeatures() { - return this.serviceDiscoveryResult != null ? this.serviceDiscoveryResult.features : Collections.emptyList(); + return this.serviceDiscoveryResult != null + ? this.serviceDiscoveryResult.features + : Collections.emptyList(); } public boolean nonanonymous() { @@ -240,7 +249,8 @@ public class MucOptions { break; } } - boolean self = user.realJid != null && user.realJid.equals(account.getJid().asBareJid()); + boolean self = + user.realJid != null && user.realJid.equals(account.getJid().asBareJid()); if (membersOnly() && nonanonymous() && user.affiliation.ranks(Affiliation.MEMBER) @@ -257,7 +267,7 @@ public class MucOptions { return user; } - //returns true if real jid was new; + // returns true if real jid was new; public boolean updateUser(User user) { User old; boolean realJidFound = false; @@ -266,7 +276,7 @@ public class MucOptions { realJidFound = old != null; if (old != null) { if (old.fullJid != null) { - return false; //don't add. user already exists + return false; // don't add. user already exists } else { synchronized (users) { users.remove(old); @@ -288,7 +298,10 @@ public class MucOptions { if (old != null) { users.remove(old); } - boolean fullJidIsSelf = isOnline && user.getFullJid() != null && user.getFullJid().equals(self.getFullJid()); + boolean fullJidIsSelf = + isOnline + && user.getFullJid() != null + && user.getFullJid().equals(self.getFullJid()); if ((!membersOnly() || user.getAffiliation().ranks(Affiliation.MEMBER)) && user.getAffiliation().outranks(Affiliation.OUTCAST) && !fullJidIsSelf) { @@ -329,7 +342,9 @@ public class MucOptions { public User findUserByOccupantId(final String occupantId) { synchronized (this.users) { - return Strings.isNullOrEmpty(occupantId) ? null : Iterables.find(this.users, u -> occupantId.equals(u.occupantId),null); + return Strings.isNullOrEmpty(occupantId) + ? null + : Iterables.find(this.users, u -> occupantId.equals(u.occupantId), null); } } @@ -345,7 +360,8 @@ public class MucOptions { public User findUser(ReadByMarker readByMarker) { if (readByMarker.getRealJid() != null) { - return findOrCreateUserByRealJid(readByMarker.getRealJid().asBareJid(), readByMarker.getFullJid()); + return findOrCreateUserByRealJid( + readByMarker.getRealJid().asBareJid(), readByMarker.getFullJid()); } else if (readByMarker.getFullJid() != null) { return findUserByFullJid(readByMarker.getFullJid()); } else { @@ -361,7 +377,7 @@ public class MucOptions { if (existing != null) { return existing; } else if (reaction.from != null) { - return new User(this,reaction.from); + return new User(this, reaction.from); } else { return null; } @@ -369,7 +385,7 @@ public class MucOptions { public List<User> findUsers(final Collection<Reaction> reactions) { final ImmutableList.Builder<User> builder = new ImmutableList.Builder<>(); - for(final Reaction reaction : reactions) { + for (final Reaction reaction : reactions) { final var user = findUser(reaction); if (user != null) { builder.add(user); @@ -400,7 +416,8 @@ public class MucOptions { synchronized (users) { ArrayList<User> users = new ArrayList<>(); for (User user : this.users) { - if (!user.isDomain() && (includeOffline || user.getRole().ranks(Role.PARTICIPANT))) { + if (!user.isDomain() + && (includeOffline || user.getRole().ranks(Role.PARTICIPANT))) { users.add(user); } } @@ -429,7 +446,8 @@ public class MucOptions { jids.add(account.getJid().asBareJid()); synchronized (users) { for (User user : users) { - if (user.getRealJid() == null || (user.getRealJid().getLocal() != null && jids.add(user.getRealJid()))) { + if (user.getRealJid() == null + || (user.getRealJid().getLocal() != null && jids.add(user.getRealJid()))) { subset.add(user); } if (subset.size() >= max) { @@ -445,7 +463,8 @@ public class MucOptions { HashSet<Jid> jids = new HashSet<>(); for (User user : users) { jids.add(user.getAccount().getJid().asBareJid()); - if (user.getRealJid() == null || (user.getRealJid().getLocal() != null && jids.add(user.getRealJid()))) { + if (user.getRealJid() == null + || (user.getRealJid().getLocal() != null && jids.add(user.getRealJid()))) { subset.add(user); } if (subset.size() >= max) { @@ -477,7 +496,8 @@ public class MucOptions { public String getProposedNickPure() { final Bookmark bookmark = this.conversation.getBookmark(); - final String bookmarkedNick = normalize(account.getJid(), bookmark == null ? null : bookmark.getNick()); + final String bookmarkedNick = + normalize(account.getJid(), bookmark == null ? null : bookmark.getNick()); if (bookmarkedNick != null) { return bookmarkedNick; } else { @@ -503,7 +523,6 @@ public class MucOptions { } catch (final IllegalArgumentException e) { return null; } - } public String getActualNick() { @@ -649,7 +668,8 @@ public class MucOptions { public String getPassword() { this.password = conversation.getAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD); - if (this.password == null && conversation.getBookmark() != null + if (this.password == null + && conversation.getBookmark() != null && conversation.getBookmark().getPassword() != null) { return conversation.getBookmark().getPassword(); } else { @@ -674,7 +694,12 @@ public class MucOptions { ArrayList<Jid> members = new ArrayList<>(); synchronized (users) { for (User user : users) { - if (user.affiliation.ranks(Affiliation.MEMBER) && user.realJid != null && !user.realJid.asBareJid().equals(conversation.account.getJid().asBareJid()) && (!user.isDomain() || includeDomains)) { + if (user.affiliation.ranks(Affiliation.MEMBER) + && user.realJid != null + && !user.realJid + .asBareJid() + .equals(conversation.account.getJid().asBareJid()) + && (!user.isDomain() || includeDomains)) { members.add(user.realJid); } } @@ -790,9 +815,7 @@ public class MucOptions { void onFailure(); } - public interface OnRenameListener extends OnEventListener { - - } + public interface OnRenameListener extends OnEventListener {} public static class User implements Comparable<User>, AvatarService.Avatarable { private Role role = Role.NONE; @@ -867,7 +890,10 @@ public class MucOptions { if (avatar != null) { return avatar.getFilename(); } - Avatar avatar = realJid != null ? getAccount().getRoster().getContact(realJid).getAvatar() : null; + Avatar avatar = + realJid != null + ? getAccount().getRoster().getContact(realJid).getAvatar() + : null; return avatar == null ? null : avatar.getFilename(); } @@ -895,7 +921,6 @@ public class MucOptions { if (realJid != null ? !realJid.equals(user.realJid) : user.realJid != null) return false; return fullJid != null ? fullJid.equals(user.fullJid) : user.fullJid == null; - } public boolean isDomain() { @@ -913,7 +938,13 @@ public class MucOptions { @Override public String toString() { - return "[fulljid:" + fullJid + ",realjid:" + realJid + ",affiliation" + affiliation.toString() + "]"; + return "[fulljid:" + + fullJid + + ",realjid:" + + realJid + + ",affiliation" + + affiliation.toString() + + "]"; } public boolean realJidMatchesAccount() { diff --git a/src/main/java/eu/siacs/conversations/entities/RawBlockable.java b/src/main/java/eu/siacs/conversations/entities/RawBlockable.java index 664a6a1c9..97f63d99c 100644 --- a/src/main/java/eu/siacs/conversations/entities/RawBlockable.java +++ b/src/main/java/eu/siacs/conversations/entities/RawBlockable.java @@ -2,14 +2,12 @@ package eu.siacs.conversations.entities; import android.content.Context; import android.text.TextUtils; - +import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.xmpp.Jid; import java.util.Collections; import java.util.List; import java.util.Locale; -import eu.siacs.conversations.utils.UIHelper; -import eu.siacs.conversations.xmpp.Jid; - public class RawBlockable implements ListItem, Blockable { private final Account account; @@ -40,7 +38,7 @@ public class RawBlockable implements ListItem, Blockable { if (jid.isFullJid()) { return jid.getResource(); } else { - return jid.toEscapedString(); + return jid.toString(); } } @@ -62,7 +60,7 @@ public class RawBlockable implements ListItem, Blockable { needle = needle.toLowerCase(Locale.US).trim(); String[] parts = needle.split("\\s+"); for (String part : parts) { - if (!jid.toEscapedString().contains(part)) { + if (!jid.toString().contains(part)) { return false; } } @@ -76,7 +74,7 @@ public class RawBlockable implements ListItem, Blockable { @Override public int getAvatarBackgroundColor() { - return UIHelper.getColorForName(jid.toEscapedString()); + return UIHelper.getColorForName(jid.toString()); } @Override @@ -86,7 +84,6 @@ public class RawBlockable implements ListItem, Blockable { @Override public int compareTo(ListItem o) { - return this.getDisplayName().compareToIgnoreCase( - o.getDisplayName()); + return this.getDisplayName().compareToIgnoreCase(o.getDisplayName()); } -} \ No newline at end of file +} diff --git a/src/main/java/eu/siacs/conversations/entities/Reaction.java b/src/main/java/eu/siacs/conversations/entities/Reaction.java index 3a945675f..cd860cd1f 100644 --- a/src/main/java/eu/siacs/conversations/entities/Reaction.java +++ b/src/main/java/eu/siacs/conversations/entities/Reaction.java @@ -1,9 +1,7 @@ package eu.siacs.conversations.entities; import android.util.Log; - import androidx.annotation.NonNull; - import com.google.common.base.MoreObjects; import com.google.common.base.Strings; import com.google.common.collect.Collections2; @@ -20,11 +18,9 @@ import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; - import eu.siacs.conversations.Config; import eu.siacs.conversations.utils.Emoticons; import eu.siacs.conversations.xmpp.Jid; - import java.io.IOException; import java.util.Arrays; import java.util.Collection; @@ -137,7 +133,7 @@ public class Reaction { if (value == null) { out.nullValue(); } else { - out.value(value.toEscapedString()); + out.value(value.toString()); } } @@ -148,7 +144,7 @@ public class Reaction { return null; } else if (in.peek() == JsonToken.STRING) { final String value = in.nextString(); - return Jid.ofEscaped(value); + return Jid.of(value); } throw new IOException("Unexpected token"); } diff --git a/src/main/java/eu/siacs/conversations/entities/Room.java b/src/main/java/eu/siacs/conversations/entities/Room.java index 9e1d61fc3..c702c3189 100644 --- a/src/main/java/eu/siacs/conversations/entities/Room.java +++ b/src/main/java/eu/siacs/conversations/entities/Room.java @@ -3,7 +3,6 @@ package eu.siacs.conversations.entities; import com.google.common.base.Objects; import com.google.common.base.Strings; import com.google.common.collect.ComparisonChain; - import eu.siacs.conversations.services.AvatarService; import eu.siacs.conversations.utils.LanguageUtils; import eu.siacs.conversations.utils.UIHelper; @@ -25,9 +24,7 @@ public class Room implements AvatarService.Avatarable, Comparable<Room> { this.nusers = nusers; } - public Room() { - - } + public Room() {} public String getName() { return name; @@ -52,7 +49,7 @@ public class Room implements AvatarService.Avatarable, Comparable<Room> { @Override public int getAvatarBackgroundColor() { Jid room = getRoom(); - return UIHelper.getColorForName(room != null ? room.asBareJid().toEscapedString() : name); + return UIHelper.getColorForName(room != null ? room.asBareJid().toString() : name); } @Override @@ -65,9 +62,9 @@ public class Room implements AvatarService.Avatarable, Comparable<Room> { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Room room = (Room) o; - return Objects.equal(address, room.address) && - Objects.equal(name, room.name) && - Objects.equal(description, room.description); + return Objects.equal(address, room.address) + && Objects.equal(name, room.name) + && Objects.equal(description, room.description); } @Override @@ -75,7 +72,6 @@ public class Room implements AvatarService.Avatarable, Comparable<Room> { return Objects.hashCode(address, name, description); } - public boolean contains(String needle) { return Strings.nullToEmpty(name).contains(needle) || Strings.nullToEmpty(description).contains(needle) @@ -90,4 +86,4 @@ public class Room implements AvatarService.Avatarable, Comparable<Room> { .compare(Strings.nullToEmpty(address), Strings.nullToEmpty(o.address)) .result(); } -} \ No newline at end of file +} diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 85e3a0d7c..9a4dad2a5 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -1,25 +1,8 @@ package eu.siacs.conversations.generator; - import android.os.Bundle; import android.util.Base64; import android.util.Log; - -import org.whispersystems.libsignal.IdentityKey; -import org.whispersystems.libsignal.ecc.ECPublicKey; -import org.whispersystems.libsignal.state.PreKeyRecord; -import org.whispersystems.libsignal.state.SignedPreKeyRecord; - -import java.nio.ByteBuffer; -import java.security.cert.CertificateEncodingException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Set; -import java.util.TimeZone; -import java.util.UUID; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.axolotl.AxolotlService; @@ -35,6 +18,19 @@ 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 java.nio.ByteBuffer; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.TimeZone; +import java.util.UUID; +import org.whispersystems.libsignal.IdentityKey; +import org.whispersystems.libsignal.ecc.ECPublicKey; +import org.whispersystems.libsignal.state.PreKeyRecord; +import org.whispersystems.libsignal.state.SignedPreKeyRecord; public class IqGenerator extends AbstractGenerator { @@ -152,7 +148,7 @@ public class IqGenerator extends AbstractGenerator { final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB); final Element retract = pubsub.addChild("retract"); retract.setAttribute("node", node); - retract.setAttribute("notify","true"); + retract.setAttribute("notify", "true"); retract.addChild("item").setAttribute("id", id); return packet; } @@ -165,7 +161,8 @@ public class IqGenerator extends AbstractGenerator { return publish(Namespace.AVATAR_DATA, item, options); } - public Iq publishElement(final String namespace, final Element element, String id, final Bundle options) { + public Iq publishElement( + final String namespace, final Element element, String id, final Bundle options) { final Element item = new Element("item"); item.setAttribute("id", id); item.addChild(element); @@ -175,8 +172,7 @@ public class IqGenerator extends AbstractGenerator { public Iq publishAvatarMetadata(final Avatar avatar, final Bundle options) { final Element item = new Element("item"); item.setAttribute("id", avatar.sha1sum); - final Element metadata = item - .addChild("metadata", Namespace.AVATAR_METADATA); + final Element metadata = item.addChild("metadata", Namespace.AVATAR_METADATA); final Element info = metadata.addChild("info"); info.setAttribute("bytes", avatar.size); info.setAttribute("id", avatar.sha1sum); @@ -263,7 +259,7 @@ public class IqGenerator extends AbstractGenerator { if (password != null) { conference.addChild("password").setContent(password); } - conference.setAttribute("autojoin",String.valueOf(autojoin)); + conference.setAttribute("autojoin", String.valueOf(autojoin)); conference.addChild(bookmark.getExtensions()); return conference; } @@ -286,8 +282,12 @@ public class IqGenerator extends AbstractGenerator { return displayed; } - public Iq publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey, - final Set<PreKeyRecord> preKeyRecords, final int deviceId, Bundle publishOptions) { + public Iq publishBundles( + final SignedPreKeyRecord signedPreKeyRecord, + final IdentityKey identityKey, + final Set<PreKeyRecord> preKeyRecords, + final int deviceId, + Bundle publishOptions) { final Element item = new Element("item"); item.setAttribute("id", "current"); final Element bundle = item.addChild("bundle", AxolotlService.PEP_PREFIX); @@ -296,21 +296,26 @@ public class IqGenerator extends AbstractGenerator { ECPublicKey publicKey = signedPreKeyRecord.getKeyPair().getPublicKey(); signedPreKeyPublic.setContent(Base64.encodeToString(publicKey.serialize(), Base64.NO_WRAP)); final Element signedPreKeySignature = bundle.addChild("signedPreKeySignature"); - signedPreKeySignature.setContent(Base64.encodeToString(signedPreKeyRecord.getSignature(), Base64.NO_WRAP)); + signedPreKeySignature.setContent( + Base64.encodeToString(signedPreKeyRecord.getSignature(), Base64.NO_WRAP)); final Element identityKeyElement = bundle.addChild("identityKey"); - identityKeyElement.setContent(Base64.encodeToString(identityKey.serialize(), Base64.NO_WRAP)); + identityKeyElement.setContent( + Base64.encodeToString(identityKey.serialize(), Base64.NO_WRAP)); final Element prekeys = bundle.addChild("prekeys", AxolotlService.PEP_PREFIX); for (PreKeyRecord preKeyRecord : preKeyRecords) { final Element prekey = prekeys.addChild("preKeyPublic"); prekey.setAttribute("preKeyId", preKeyRecord.getId()); - prekey.setContent(Base64.encodeToString(preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.NO_WRAP)); + prekey.setContent( + Base64.encodeToString( + preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.NO_WRAP)); } return publish(AxolotlService.PEP_BUNDLES + ":" + deviceId, item, publishOptions); } - public Iq publishVerification(byte[] signature, X509Certificate[] certificates, final int deviceId) { + public Iq publishVerification( + byte[] signature, X509Certificate[] certificates, final int deviceId) { final Element item = new Element("item"); item.setAttribute("id", "current"); final Element verification = item.addChild("verification", AxolotlService.PEP_PREFIX); @@ -318,13 +323,16 @@ public class IqGenerator extends AbstractGenerator { for (int i = 0; i < certificates.length; ++i) { try { Element certificate = chain.addChild("certificate"); - certificate.setContent(Base64.encodeToString(certificates[i].getEncoded(), Base64.NO_WRAP)); + certificate.setContent( + Base64.encodeToString(certificates[i].getEncoded(), Base64.NO_WRAP)); certificate.setAttribute("index", i); } catch (CertificateEncodingException e) { Log.d(Config.LOGTAG, "could not encode certificate"); } } - verification.addChild("signature").setContent(Base64.encodeToString(signature, Base64.NO_WRAP)); + verification + .addChild("signature") + .setContent(Base64.encodeToString(signature, Base64.NO_WRAP)); return publish(AxolotlService.PEP_VERIFICATION + ":" + deviceId, item); } @@ -337,7 +345,7 @@ public class IqGenerator extends AbstractGenerator { if (mam.muc()) { packet.setTo(mam.getWith()); } else if (mam.getWith() != null) { - data.put("with", mam.getWith().toEscapedString()); + data.put("with", mam.getWith().toString()); } final long start = mam.getStart(); final long end = mam.getEnd(); @@ -366,7 +374,8 @@ public class IqGenerator extends AbstractGenerator { return iq; } - public Iq generateSetBlockRequest(final Jid jid, final boolean reportSpam, final String serverMsgId) { + public Iq generateSetBlockRequest( + final Jid jid, final boolean reportSpam, final String serverMsgId) { final Iq iq = new Iq(Iq.Type.SET); final Element block = iq.addChild("block", Namespace.BLOCKING); final Element item = block.addChild("item").setAttribute("jid", jid); @@ -457,7 +466,9 @@ public class IqGenerator extends AbstractGenerator { ByteBuffer bb = ByteBuffer.wrap(new byte[16]); bb.putLong(uuid.getMostSignificantBits()); bb.putLong(uuid.getLeastSignificantBits()); - return Base64.encodeToString(bb.array(), Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP) + name.substring(pos); + return Base64.encodeToString( + bb.array(), Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP) + + name.substring(pos); } catch (Exception e) { return name; } @@ -466,7 +477,8 @@ public class IqGenerator extends AbstractGenerator { } } - public static Iq generateCreateAccountWithCaptcha(final Account account, final String id, final Data data) { + public static Iq generateCreateAccountWithCaptcha( + final Account account, final String id, final Data data) { final Iq register = new Iq(Iq.Type.SET); register.setFrom(account.getJid().asBareJid()); register.setTo(account.getDomain()); @@ -492,7 +504,7 @@ public class IqGenerator extends AbstractGenerator { data.put("token", token); data.put("android-id", deviceId); if (muc != null) { - data.put("muc", muc.toEscapedString()); + data.put("muc", muc.toString()); } data.submit(); command.addChild(data); @@ -539,7 +551,9 @@ public class IqGenerator extends AbstractGenerator { public Iq queryAffiliation(Conversation conversation, String affiliation) { final Iq packet = new Iq(Iq.Type.GET); packet.setTo(conversation.getJid().asBareJid()); - packet.query("http://jabber.org/protocol/muc#admin").addChild("item").setAttribute("affiliation", affiliation); + packet.query("http://jabber.org/protocol/muc#admin") + .addChild("item") + .setAttribute("affiliation", affiliation); return packet; } @@ -551,9 +565,9 @@ public class IqGenerator extends AbstractGenerator { options.putString("muc#roomconfig_whois", "anyone"); options.putString("muc#roomconfig_changesubject", "0"); options.putString("muc#roomconfig_allowinvites", "0"); - options.putString("muc#roomconfig_enablearchiving", "1"); //prosody - options.putString("mam", "1"); //ejabberd community - options.putString("muc#roomconfig_mam", "1"); //ejabberd saas + options.putString("muc#roomconfig_enablearchiving", "1"); // prosody + options.putString("mam", "1"); // ejabberd community + options.putString("muc#roomconfig_mam", "1"); // ejabberd saas return options; } @@ -564,9 +578,9 @@ public class IqGenerator extends AbstractGenerator { options.putString("muc#roomconfig_publicroom", "1"); options.putString("muc#roomconfig_whois", "moderators"); options.putString("muc#roomconfig_changesubject", "0"); - options.putString("muc#roomconfig_enablearchiving", "1"); //prosody - options.putString("mam", "1"); //ejabberd community - options.putString("muc#roomconfig_mam", "1"); //ejabberd saas + options.putString("muc#roomconfig_enablearchiving", "1"); // prosody + options.putString("mam", "1"); // ejabberd community + options.putString("muc#roomconfig_mam", "1"); // ejabberd saas return options; } @@ -592,14 +606,14 @@ public class IqGenerator extends AbstractGenerator { public Iq queryDiscoItems(final Jid jid) { final Iq packet = new Iq(Iq.Type.GET); packet.setTo(jid); - packet.addChild("query",Namespace.DISCO_ITEMS); + packet.addChild("query", Namespace.DISCO_ITEMS); return packet; } public Iq queryDiscoInfo(final Jid jid) { final Iq packet = new Iq(Iq.Type.GET); packet.setTo(jid); - packet.addChild("query",Namespace.DISCO_INFO); + packet.addChild("query", Namespace.DISCO_INFO); return packet; } } diff --git a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java index ac42857fd..8f5178d8e 100644 --- a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java +++ b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java @@ -1,93 +1,93 @@ package eu.siacs.conversations.parser; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Locale; - import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; -import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; import im.conversations.android.xmpp.model.stanza.Stanza; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; public abstract class AbstractParser { - protected final XmppConnectionService mXmppConnectionService; - protected final Account account; - - protected AbstractParser(final XmppConnectionService service, final Account account) { - this.mXmppConnectionService = service; - this.account = account; - } - - public static Long parseTimestamp(Element element, Long d) { - return parseTimestamp(element,d,false); - } - - public static Long parseTimestamp(Element element, Long d, boolean ignoreCsiAndSm) { - long min = Long.MAX_VALUE; - boolean returnDefault = true; - final Jid to; - if (ignoreCsiAndSm && element instanceof Stanza stanza) { - to = stanza.getTo(); - } else { - to = null; - } - for(Element child : element.getChildren()) { - if ("delay".equals(child.getName()) && "urn:xmpp:delay".equals(child.getNamespace())) { - final Jid f = to == null ? null : InvalidJid.getNullForInvalid(child.getAttributeAsJid("from")); - if (f != null && (to.asBareJid().equals(f) || to.getDomain().equals(f))) { - continue; - } - final String stamp = child.getAttribute("stamp"); - if (stamp != null) { - try { - min = Math.min(min,AbstractParser.parseTimestamp(stamp)); - returnDefault = false; - } catch (Throwable t) { - //ignore - } - } - } - } - if (returnDefault) { - return d; - } else { - return min; - } - } - - public static long parseTimestamp(Element element) { - return parseTimestamp(element, System.currentTimeMillis()); - } - - public static long parseTimestamp(String timestamp) throws ParseException { - timestamp = timestamp.replace("Z", "+0000"); - SimpleDateFormat dateFormat; - long ms; - if (timestamp.length() >= 25 && timestamp.charAt(19) == '.') { - String millis = timestamp.substring(19,timestamp.length() - 5); - try { - double fractions = Double.parseDouble("0" + millis); - ms = Math.round(1000 * fractions); - } catch (NumberFormatException e) { - ms = 0; - } - } else { - ms = 0; - } - timestamp = timestamp.substring(0,19)+timestamp.substring(timestamp.length() -5); - dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ",Locale.US); - return Math.min(dateFormat.parse(timestamp).getTime()+ms, System.currentTimeMillis()); - } + protected final XmppConnectionService mXmppConnectionService; + protected final Account account; + + protected AbstractParser(final XmppConnectionService service, final Account account) { + this.mXmppConnectionService = service; + this.account = account; + } + + public static Long parseTimestamp(Element element, Long d) { + return parseTimestamp(element, d, false); + } + + public static Long parseTimestamp(Element element, Long d, boolean ignoreCsiAndSm) { + long min = Long.MAX_VALUE; + boolean returnDefault = true; + final Jid to; + if (ignoreCsiAndSm && element instanceof Stanza stanza) { + to = stanza.getTo(); + } else { + to = null; + } + for (Element child : element.getChildren()) { + if ("delay".equals(child.getName()) && "urn:xmpp:delay".equals(child.getNamespace())) { + final Jid f = + to == null + ? null + : Jid.Invalid.getNullForInvalid(child.getAttributeAsJid("from")); + if (f != null && (to.asBareJid().equals(f) || to.getDomain().equals(f))) { + continue; + } + final String stamp = child.getAttribute("stamp"); + if (stamp != null) { + try { + min = Math.min(min, AbstractParser.parseTimestamp(stamp)); + returnDefault = false; + } catch (Throwable t) { + // ignore + } + } + } + } + if (returnDefault) { + return d; + } else { + return min; + } + } + + public static long parseTimestamp(Element element) { + return parseTimestamp(element, System.currentTimeMillis()); + } + + public static long parseTimestamp(String timestamp) throws ParseException { + timestamp = timestamp.replace("Z", "+0000"); + SimpleDateFormat dateFormat; + long ms; + if (timestamp.length() >= 25 && timestamp.charAt(19) == '.') { + String millis = timestamp.substring(19, timestamp.length() - 5); + try { + double fractions = Double.parseDouble("0" + millis); + ms = Math.round(1000 * fractions); + } catch (NumberFormatException e) { + ms = 0; + } + } else { + ms = 0; + } + timestamp = timestamp.substring(0, 19) + timestamp.substring(timestamp.length() - 5); + dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US); + return Math.min(dateFormat.parse(timestamp).getTime() + ms, System.currentTimeMillis()); + } public static long getTimestamp(final String input) throws ParseException { if (input == null) { @@ -120,93 +120,94 @@ public abstract class AbstractParser { } } - protected void updateLastseen(final Account account, final Jid from) { - final Contact contact = account.getRoster().getContact(from); - contact.setLastResource(from.isBareJid() ? "" : from.getResource()); - } - - protected static String avatarData(Element items) { - Element item = items.findChild("item"); - if (item == null) { - return null; - } - return item.findChildContent("data", "urn:xmpp:avatar:data"); - } - - public static MucOptions.User parseItem(Conversation conference, Element item) { - return parseItem(conference,item, null); - } - - public static MucOptions.User parseItem(final Conversation conference, Element item, Jid fullJid) { - final String local = conference.getJid().getLocal(); - final String domain = conference.getJid().getDomain().toEscapedString(); - String affiliation = item.getAttribute("affiliation"); - String role = item.getAttribute("role"); - String nick = item.getAttribute("nick"); - if (nick != null && fullJid == null) { - try { - fullJid = Jid.of(local, domain, nick); - } catch (IllegalArgumentException e) { - fullJid = null; - } - } - final Jid realJid = item.getAttributeAsJid("jid"); - MucOptions.User user = new MucOptions.User(conference.getMucOptions(), fullJid); - if (InvalidJid.isValid(realJid)) { - user.setRealJid(realJid); - } - user.setAffiliation(affiliation); - user.setRole(role); - return user; - } - - public static String extractErrorMessage(final Element packet) { - final Element error = packet.findChild("error"); - if (error != null && error.getChildren().size() > 0) { - final List<String> errorNames = orderedElementNames(error.getChildren()); - final String text = error.findChildContent("text"); - if (text != null && !text.trim().isEmpty()) { - return prefixError(errorNames)+text; - } else if (errorNames.size() > 0){ - return prefixError(errorNames)+errorNames.get(0).replace("-"," "); - } - } - return null; - } - - public static String errorMessage(Element packet) { - final Element error = packet.findChild("error"); - if (error != null && error.getChildren().size() > 0) { - final List<String> errorNames = orderedElementNames(error.getChildren()); - final String text = error.findChildContent("text"); - if (text != null && !text.trim().isEmpty()) { - return text; - } else if (errorNames.size() > 0){ - return errorNames.get(0).replace("-"," "); - } - } - return null; - } - - private static String prefixError(List<String> errorNames) { - if (errorNames.size() > 0) { - return errorNames.get(0)+'\u001f'; - } - return ""; - } - - private static List<String> orderedElementNames(List<Element> children) { - List<String> names = new ArrayList<>(); - for(Element child : children) { - final String name = child.getName(); - if (name != null && !name.equals("text")) { - if ("urn:ietf:params:xml:ns:xmpp-stanzas".equals(child.getNamespace())) { - names.add(name); - } else { - names.add(0, name); - } - } - } - return names; - } + protected void updateLastseen(final Account account, final Jid from) { + final Contact contact = account.getRoster().getContact(from); + contact.setLastResource(from.isBareJid() ? "" : from.getResource()); + } + + protected static String avatarData(Element items) { + Element item = items.findChild("item"); + if (item == null) { + return null; + } + return item.findChildContent("data", "urn:xmpp:avatar:data"); + } + + public static MucOptions.User parseItem(Conversation conference, Element item) { + return parseItem(conference, item, null); + } + + public static MucOptions.User parseItem( + final Conversation conference, Element item, Jid fullJid) { + final String local = conference.getJid().getLocal(); + final String domain = conference.getJid().getDomain().toString(); + String affiliation = item.getAttribute("affiliation"); + String role = item.getAttribute("role"); + String nick = item.getAttribute("nick"); + if (nick != null && fullJid == null) { + try { + fullJid = Jid.of(local, domain, nick); + } catch (IllegalArgumentException e) { + fullJid = null; + } + } + final Jid realJid = item.getAttributeAsJid("jid"); + MucOptions.User user = new MucOptions.User(conference.getMucOptions(), fullJid); + if (Jid.Invalid.isValid(realJid)) { + user.setRealJid(realJid); + } + user.setAffiliation(affiliation); + user.setRole(role); + return user; + } + + public static String extractErrorMessage(final Element packet) { + final Element error = packet.findChild("error"); + if (error != null && error.getChildren().size() > 0) { + final List<String> errorNames = orderedElementNames(error.getChildren()); + final String text = error.findChildContent("text"); + if (text != null && !text.trim().isEmpty()) { + return prefixError(errorNames) + text; + } else if (errorNames.size() > 0) { + return prefixError(errorNames) + errorNames.get(0).replace("-", " "); + } + } + return null; + } + + public static String errorMessage(Element packet) { + final Element error = packet.findChild("error"); + if (error != null && error.getChildren().size() > 0) { + final List<String> errorNames = orderedElementNames(error.getChildren()); + final String text = error.findChildContent("text"); + if (text != null && !text.trim().isEmpty()) { + return text; + } else if (errorNames.size() > 0) { + return errorNames.get(0).replace("-", " "); + } + } + return null; + } + + private static String prefixError(List<String> errorNames) { + if (errorNames.size() > 0) { + return errorNames.get(0) + '\u001f'; + } + return ""; + } + + private static List<String> orderedElementNames(List<Element> children) { + List<String> names = new ArrayList<>(); + for (Element child : children) { + final String name = child.getName(); + if (name != null && !name.equals("text")) { + if ("urn:ietf:params:xml:ns:xmpp-stanzas".equals(child.getNamespace())) { + names.add(name); + } else { + names.add(0, name); + } + } + } + return names; + } } diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java index 339c025c9..14efffd53 100644 --- a/src/main/java/eu/siacs/conversations/parser/IqParser.java +++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java @@ -3,31 +3,9 @@ package eu.siacs.conversations.parser; import android.text.TextUtils; import android.util.Log; import android.util.Pair; - import androidx.annotation.NonNull; - import com.google.common.base.CharMatcher; import com.google.common.io.BaseEncoding; - -import org.whispersystems.libsignal.IdentityKey; -import org.whispersystems.libsignal.InvalidKeyException; -import org.whispersystems.libsignal.ecc.Curve; -import org.whispersystems.libsignal.ecc.ECPublicKey; -import org.whispersystems.libsignal.state.PreKeyBundle; - -import java.io.ByteArrayInputStream; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Consumer; - import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.entities.Account; @@ -36,11 +14,27 @@ import eu.siacs.conversations.entities.Room; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.forms.Data; import im.conversations.android.xmpp.model.stanza.Iq; +import java.io.ByteArrayInputStream; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import org.whispersystems.libsignal.IdentityKey; +import org.whispersystems.libsignal.InvalidKeyException; +import org.whispersystems.libsignal.ecc.Curve; +import org.whispersystems.libsignal.ecc.ECPublicKey; +import org.whispersystems.libsignal.state.PreKeyBundle; public class IqParser extends AbstractParser implements Consumer<Iq> { @@ -94,8 +88,7 @@ public class IqParser extends AbstractParser implements Consumer<Iq> { TextUtils.isEmpty(roomName) ? name : roomName, description, language, - nusers - ); + nusers); } private void rosterItems(final Account account, final Element query) { @@ -105,14 +98,16 @@ public class IqParser extends AbstractParser implements Consumer<Iq> { } for (final Element item : query.getChildren()) { if (item.getName().equals("item")) { - final Jid jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid")); + final Jid jid = Jid.Invalid.getNullForInvalid(item.getAttributeAsJid("jid")); if (jid == null) { continue; } final String name = item.getAttribute("name"); final String subscription = item.getAttribute("subscription"); final Contact contact = account.getRoster().getContact(jid); - boolean bothPre = contact.getOption(Contact.Options.TO) && contact.getOption(Contact.Options.FROM); + boolean bothPre = + contact.getOption(Contact.Options.TO) + && contact.getOption(Contact.Options.FROM); if (!contact.getOption(Contact.Options.DIRTY_PUSH)) { contact.setServerName(name); contact.parseGroupsFromElement(item); @@ -126,9 +121,15 @@ public class IqParser extends AbstractParser implements Consumer<Iq> { contact.resetOption(Contact.Options.DIRTY_PUSH); contact.parseSubscriptionFromElement(item); } - boolean both = contact.getOption(Contact.Options.TO) && contact.getOption(Contact.Options.FROM); + boolean both = + contact.getOption(Contact.Options.TO) + && contact.getOption(Contact.Options.FROM); if ((both != bothPre) && both) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": gained mutual presence subscription with " + contact.getJid()); + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + + ": gained mutual presence subscription with " + + contact.getJid()); AxolotlService axolotlService = account.getAxolotlService(); if (axolotlService != null) { axolotlService.clearErrorsInFetchStatusMap(contact.getJid()); @@ -181,7 +182,15 @@ public class IqParser extends AbstractParser implements Consumer<Iq> { Integer id = Integer.valueOf(device.getAttribute("id")); deviceIds.add(id); } catch (NumberFormatException e) { - Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Encountered invalid <device> node in PEP (" + e.getMessage() + "):" + device.toString() + ", skipping..."); + Log.e( + Config.LOGTAG, + AxolotlService.LOGPREFIX + + " : " + + "Encountered invalid <device> node in PEP (" + + e.getMessage() + + "):" + + device.toString() + + ", skipping..."); } } } @@ -210,7 +219,12 @@ public class IqParser extends AbstractParser implements Consumer<Iq> { try { publicKey = Curve.decodePoint(base64decode(signedPreKeyPublic), 0); } catch (final IllegalArgumentException | InvalidKeyException e) { - Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Invalid signedPreKeyPublic in PEP: " + e.getMessage()); + Log.e( + Config.LOGTAG, + AxolotlService.LOGPREFIX + + " : " + + "Invalid signedPreKeyPublic in PEP: " + + e.getMessage()); } return publicKey; } @@ -223,7 +237,9 @@ public class IqParser extends AbstractParser implements Consumer<Iq> { try { return base64decode(signedPreKeySignature); } catch (final IllegalArgumentException e) { - Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : Invalid base64 in signedPreKeySignature"); + Log.e( + Config.LOGTAG, + AxolotlService.LOGPREFIX + " : Invalid base64 in signedPreKeySignature"); return null; } } @@ -236,7 +252,12 @@ public class IqParser extends AbstractParser implements Consumer<Iq> { try { return new IdentityKey(base64decode(identityKey), 0); } catch (final IllegalArgumentException | InvalidKeyException e) { - Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Invalid identityKey in PEP: " + e.getMessage()); + Log.e( + Config.LOGTAG, + AxolotlService.LOGPREFIX + + " : " + + "Invalid identityKey in PEP: " + + e.getMessage()); return null; } } @@ -245,7 +266,12 @@ public class IqParser extends AbstractParser implements Consumer<Iq> { Map<Integer, ECPublicKey> preKeyRecords = new HashMap<>(); Element item = getItem(packet); if (item == null) { - Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Couldn't find <item> in bundle IQ packet: " + packet); + Log.d( + Config.LOGTAG, + AxolotlService.LOGPREFIX + + " : " + + "Couldn't find <item> in bundle IQ packet: " + + packet); return null; } final Element bundleElement = item.findChild("bundle"); @@ -254,12 +280,22 @@ public class IqParser extends AbstractParser implements Consumer<Iq> { } final Element prekeysElement = bundleElement.findChild("prekeys"); if (prekeysElement == null) { - Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Couldn't find <prekeys> in bundle IQ packet: " + packet); + Log.d( + Config.LOGTAG, + AxolotlService.LOGPREFIX + + " : " + + "Couldn't find <prekeys> in bundle IQ packet: " + + packet); return null; } for (Element preKeyPublicElement : prekeysElement.getChildren()) { if (!preKeyPublicElement.getName().equals("preKeyPublic")) { - Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Encountered unexpected tag in prekeys list: " + preKeyPublicElement); + Log.d( + Config.LOGTAG, + AxolotlService.LOGPREFIX + + " : " + + "Encountered unexpected tag in prekeys list: " + + preKeyPublicElement); continue; } final String preKey = preKeyPublicElement.getContent(); @@ -272,9 +308,22 @@ public class IqParser extends AbstractParser implements Consumer<Iq> { final ECPublicKey preKeyPublic = Curve.decodePoint(base64decode(preKey), 0); preKeyRecords.put(preKeyId, preKeyPublic); } catch (NumberFormatException e) { - Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "could not parse preKeyId from preKey " + preKeyPublicElement.toString()); + Log.e( + Config.LOGTAG, + AxolotlService.LOGPREFIX + + " : " + + "could not parse preKeyId from preKey " + + preKeyPublicElement.toString()); } catch (Throwable e) { - Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Invalid preKeyPublic (ID=" + preKeyId + ") in PEP: " + e.getMessage() + ", skipping..."); + Log.e( + Config.LOGTAG, + AxolotlService.LOGPREFIX + + " : " + + "Invalid preKeyPublic (ID=" + + preKeyId + + ") in PEP: " + + e.getMessage() + + ", skipping..."); } } return preKeyRecords; @@ -286,7 +335,8 @@ public class IqParser extends AbstractParser implements Consumer<Iq> { public static Pair<X509Certificate[], byte[]> verification(final Iq packet) { Element item = getItem(packet); - Element verification = item != null ? item.findChild("verification", AxolotlService.PEP_PREFIX) : null; + Element verification = + item != null ? item.findChild("verification", AxolotlService.PEP_PREFIX) : null; Element chain = verification != null ? verification.findChild("chain") : null; String signature = verification != null ? verification.findChildContent("signature") : null; if (chain != null && signature != null) { @@ -300,7 +350,11 @@ public class IqParser extends AbstractParser implements Consumer<Iq> { if (cert == null) { continue; } - certificates[i] = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(BaseEncoding.base64().decode(cert))); + certificates[i] = + (X509Certificate) + certificateFactory.generateCertificate( + new ByteArrayInputStream( + BaseEncoding.base64().decode(cert))); ++i; } return new Pair<>(certificates, BaseEncoding.base64().decode(signature)); @@ -332,8 +386,15 @@ public class IqParser extends AbstractParser implements Consumer<Iq> { || signedPreKeySignature.length == 0) { return null; } - return new PreKeyBundle(0, 0, 0, null, - signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey); + return new PreKeyBundle( + 0, + 0, + 0, + null, + signedPreKeyId, + signedPreKeyPublic, + signedPreKeySignature, + identityKey); } public static List<PreKeyBundle> preKeys(final Iq preKeys) { @@ -342,8 +403,7 @@ public class IqParser extends AbstractParser implements Consumer<Iq> { if (preKeyPublics != null) { for (Integer preKeyId : preKeyPublics.keySet()) { ECPublicKey preKeyPublic = preKeyPublics.get(preKeyId); - bundles.add(new PreKeyBundle(0, 0, preKeyId, preKeyPublic, - 0, null, null, null)); + bundles.add(new PreKeyBundle(0, 0, preKeyId, preKeyPublic, 0, null, null, null)); } } @@ -363,15 +423,19 @@ public class IqParser extends AbstractParser implements Consumer<Iq> { account.getRoster().markAllAsNotInRoster(); } this.rosterItems(account, query); - } else if ((packet.hasChild("block", Namespace.BLOCKING) || packet.hasChild("blocklist", Namespace.BLOCKING)) && - packet.fromServer(account)) { + } else if ((packet.hasChild("block", Namespace.BLOCKING) + || packet.hasChild("blocklist", Namespace.BLOCKING)) + && packet.fromServer(account)) { // Block list or block push. Log.d(Config.LOGTAG, "Received blocklist update from server"); final Element blocklist = packet.findChild("blocklist", Namespace.BLOCKING); final Element block = packet.findChild("block", Namespace.BLOCKING); - final Collection<Element> items = blocklist != null ? blocklist.getChildren() : - (block != null ? block.getChildren() : null); - // If this is a response to a blocklist query, clear the block list and replace with the new one. + final Collection<Element> items = + blocklist != null + ? blocklist.getChildren() + : (block != null ? block.getChildren() : null); + // If this is a response to a blocklist query, clear the block list and replace with the + // new one. // Otherwise, just update the existing blocklist. if (packet.getType() == Iq.Type.RESULT) { account.clearBlocklist(); @@ -382,7 +446,8 @@ public class IqParser extends AbstractParser implements Consumer<Iq> { // Create a collection of Jids from the packet for (final Element item : items) { if (item.getName().equals("item")) { - final Jid jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid")); + final Jid jid = + Jid.Invalid.getNullForInvalid(item.getAttributeAsJid("jid")); if (jid != null) { jids.add(jid); } @@ -405,10 +470,12 @@ public class IqParser extends AbstractParser implements Consumer<Iq> { final Iq response = packet.generateResponse(Iq.Type.RESULT); mXmppConnectionService.sendIqPacket(account, response, null); } - } else if (packet.hasChild("unblock", Namespace.BLOCKING) && - packet.fromServer(account) && packet.getType() == Iq.Type.SET) { + } else if (packet.hasChild("unblock", Namespace.BLOCKING) + && packet.fromServer(account) + && packet.getType() == Iq.Type.SET) { Log.d(Config.LOGTAG, "Received unblock update from server"); - final Collection<Element> items = packet.findChild("unblock", Namespace.BLOCKING).getChildren(); + final Collection<Element> items = + packet.findChild("unblock", Namespace.BLOCKING).getChildren(); if (items.isEmpty()) { // No children to unblock == unblock all account.getBlocklist().clear(); @@ -416,7 +483,8 @@ public class IqParser extends AbstractParser implements Consumer<Iq> { final Collection<Jid> jids = new ArrayList<>(items.size()); for (final Element item : items) { if (item.getName().equals("item")) { - final Jid jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid")); + final Jid jid = + Jid.Invalid.getNullForInvalid(item.getAttributeAsJid("jid")); if (jid != null) { jids.add(jid); } @@ -430,10 +498,10 @@ public class IqParser extends AbstractParser implements Consumer<Iq> { } else if (packet.hasChild("open", "http://jabber.org/protocol/ibb") || packet.hasChild("data", "http://jabber.org/protocol/ibb") || packet.hasChild("close", "http://jabber.org/protocol/ibb")) { - mXmppConnectionService.getJingleConnectionManager() - .deliverIbbPacket(account, packet); + mXmppConnectionService.getJingleConnectionManager().deliverIbbPacket(account, packet); } else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) { - final Iq response = mXmppConnectionService.getIqGenerator().discoResponse(account, packet); + final Iq response = + mXmppConnectionService.getIqGenerator().discoResponse(account, packet); mXmppConnectionService.sendIqPacket(account, response, null); } else if (packet.hasChild("query", "jabber:iq:version") && isGet) { final Iq response = mXmppConnectionService.getIqGenerator().versionResponse(packet); @@ -452,7 +520,8 @@ public class IqParser extends AbstractParser implements Consumer<Iq> { response = mXmppConnectionService.getIqGenerator().entityTimeResponse(packet); } mXmppConnectionService.sendIqPacket(account, response, null); - } else if (packet.hasChild("push", Namespace.UNIFIED_PUSH) && packet.getType() == Iq.Type.SET) { + } else if (packet.hasChild("push", Namespace.UNIFIED_PUSH) + && packet.getType() == Iq.Type.SET) { final Jid transport = packet.getFrom(); final Element push = packet.findChild("push", Namespace.UNIFIED_PUSH); final boolean success = @@ -480,5 +549,4 @@ public class IqParser extends AbstractParser implements Consumer<Iq> { } } } - } diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 80430e384..9188c1b66 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -31,7 +31,6 @@ import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.LocalizedContent; import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.chatstate.ChatState; import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager; @@ -95,7 +94,7 @@ public class MessageParser extends AbstractParser for (Element child : packet.getChildren()) { if (child.getName().equals("stanza-id") && Namespace.STANZA_IDS.equals(child.getNamespace()) - && by.equals(InvalidJid.getNullForInvalid(child.getAttributeAsJid("by")))) { + && by.equals(Jid.Invalid.getNullForInvalid(child.getAttributeAsJid("by")))) { return child.getAttribute("id"); } } @@ -105,7 +104,7 @@ public class MessageParser extends AbstractParser private static Jid getTrueCounterpart(Element mucUserElement, Jid fallback) { final Element item = mucUserElement == null ? null : mucUserElement.findChild("item"); Jid result = - item == null ? null : InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid")); + item == null ? null : Jid.Invalid.getNullForInvalid(item.getAttributeAsJid("jid")); return result != null ? result : fallback; } @@ -154,7 +153,7 @@ public class MessageParser extends AbstractParser final XmppAxolotlMessage xmppAxolotlMessage; try { xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlMessage, from.asBareJid()); - } catch (Exception e) { + } catch (final Exception e) { Log.d( Config.LOGTAG, conversation.getAccount().getJid().asBareJid() @@ -224,13 +223,13 @@ public class MessageParser extends AbstractParser final Element invite = mucUser.findChild("invite"); if (invite != null) { final String password = mucUser.findChildContent("password"); - final Jid from = InvalidJid.getNullForInvalid(invite.getAttributeAsJid("from")); - final Jid to = InvalidJid.getNullForInvalid(invite.getAttributeAsJid("to")); + final Jid from = Jid.Invalid.getNullForInvalid(invite.getAttributeAsJid("from")); + final Jid to = Jid.Invalid.getNullForInvalid(invite.getAttributeAsJid("to")); if (to != null && from == null) { Log.d(Config.LOGTAG, "do not parse outgoing mediated invite " + message); return null; } - final Jid room = InvalidJid.getNullForInvalid(message.getAttributeAsJid("from")); + final Jid room = Jid.Invalid.getNullForInvalid(message.getAttributeAsJid("from")); if (room == null) { return null; } @@ -239,8 +238,8 @@ public class MessageParser extends AbstractParser } final Element conference = message.findChild("x", "jabber:x:conference"); if (conference != null) { - Jid from = InvalidJid.getNullForInvalid(message.getAttributeAsJid("from")); - Jid room = InvalidJid.getNullForInvalid(conference.getAttributeAsJid("jid")); + Jid from = Jid.Invalid.getNullForInvalid(message.getAttributeAsJid("from")); + Jid room = Jid.Invalid.getNullForInvalid(conference.getAttributeAsJid("jid")); if (room == null) { return null; } @@ -333,7 +332,7 @@ public class MessageParser extends AbstractParser } } if (retract != null) { - final Jid id = InvalidJid.getNullForInvalid(retract.getAttributeAsJid("id")); + final Jid id = Jid.Invalid.getNullForInvalid(retract.getAttributeAsJid("id")); if (id != null) { account.removeBookmark(id); Log.d( @@ -567,7 +566,7 @@ public class MessageParser extends AbstractParser } boolean notify = false; - if (from == null || !InvalidJid.isValid(from) || !InvalidJid.isValid(to)) { + if (from == null || !Jid.Invalid.isValid(from) || !Jid.Invalid.isValid(to)) { Log.e(Config.LOGTAG, "encountered invalid message from='" + from + "' to='" + to + "'"); return; } @@ -607,7 +606,7 @@ public class MessageParser extends AbstractParser occupant = null; } boolean isMucStatusMessage = - InvalidJid.hasValidFrom(packet) + Jid.Invalid.hasValidFrom(packet) && from.isBareJid() && mucUserElement != null && mucUserElement.hasChild("status"); @@ -668,7 +667,7 @@ public class MessageParser extends AbstractParser || mucUserElement != null || account.getXmppConnection() .getMucServersWithholdAccount() - .contains(counterpart.getDomain().toEscapedString()); + .contains(counterpart.getDomain().toString()); final Conversation conversation = mXmppConnectionService.findOrCreateConversation( account, @@ -1161,7 +1160,7 @@ public class MessageParser extends AbstractParser } if (conversation != null && mucUserElement != null - && InvalidJid.hasValidFrom(packet) + && Jid.Invalid.hasValidFrom(packet) && from.isBareJid()) { for (Element child : mucUserElement.getChildren()) { if ("status".equals(child.getName())) { @@ -1381,7 +1380,7 @@ public class MessageParser extends AbstractParser final Element event = original.findChild("event", "http://jabber.org/protocol/pubsub#event"); - if (event != null && InvalidJid.hasValidFrom(original) && original.getFrom().isBareJid()) { + if (event != null && Jid.Invalid.hasValidFrom(original) && original.getFrom().isBareJid()) { if (event.hasChild("items")) { parseEvent(event, original.getFrom(), account); } else if (event.hasChild("delete")) { @@ -1392,7 +1391,7 @@ public class MessageParser extends AbstractParser } final String nick = packet.findChildContent("nick", Namespace.NICK); - if (nick != null && InvalidJid.hasValidFrom(original)) { + if (nick != null && Jid.Invalid.hasValidFrom(original)) { if (mXmppConnectionService.isMuc(account, from)) { return; } @@ -1444,7 +1443,7 @@ public class MessageParser extends AbstractParser Jid from) { final var id = displayed.getId(); // TODO we don’t even use 'sender' any more. Remove this! - final Jid sender = InvalidJid.getNullForInvalid(displayed.getAttributeAsJid("sender")); + final Jid sender = Jid.Invalid.getNullForInvalid(displayed.getAttributeAsJid("sender")); if (packet.fromAccount(account) && !selfAddressed) { final Conversation c = mXmppConnectionService.find(account, counterpart.asBareJid()); final Message message = diff --git a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java index e50d1096c..088938402 100644 --- a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java +++ b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java @@ -1,7 +1,6 @@ package eu.siacs.conversations.parser; import android.util.Log; - import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.PgpEngine; import eu.siacs.conversations.crypto.axolotl.AxolotlService; @@ -17,24 +16,23 @@ import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.pep.Avatar; import im.conversations.android.xmpp.model.occupant.OccupantId; - -import org.openintents.openpgp.util.OpenPgpUtils; - import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; +import org.openintents.openpgp.util.OpenPgpUtils; -public class PresenceParser extends AbstractParser implements Consumer<im.conversations.android.xmpp.model.stanza.Presence> { +public class PresenceParser extends AbstractParser + implements Consumer<im.conversations.android.xmpp.model.stanza.Presence> { public PresenceParser(final XmppConnectionService service, final Account account) { super(service, account); } - public void parseConferencePresence(final im.conversations.android.xmpp.model.stanza.Presence packet, Account account) { + public void parseConferencePresence( + final im.conversations.android.xmpp.model.stanza.Presence packet, Account account) { final Conversation conversation = packet.getFrom() == null ? null @@ -58,7 +56,9 @@ public class PresenceParser extends AbstractParser implements Consumer<im.conver } } - private void processConferencePresence(final im.conversations.android.xmpp.model.stanza.Presence packet, Conversation conversation) { + private void processConferencePresence( + final im.conversations.android.xmpp.model.stanza.Presence packet, + Conversation conversation) { final Account account = conversation.getAccount(); final MucOptions mucOptions = conversation.getMucOptions(); final Jid jid = conversation.getAccount().getJid(); @@ -75,12 +75,15 @@ public class PresenceParser extends AbstractParser implements Consumer<im.conver mucOptions.setError(MucOptions.Error.NONE); final MucOptions.User user = parseItem(conversation, item, from); final var occupant = packet.getExtension(OccupantId.class); - final String occupantId = mucOptions.occupantId() && occupant != null ? occupant.getId() : null; + final String occupantId = + mucOptions.occupantId() && occupant != null + ? occupant.getId() + : null; user.setOccupantId(occupantId); if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE) || (codes.contains(MucOptions.STATUS_CODE_ROOM_CREATED) && jid.equals( - InvalidJid.getNullForInvalid( + Jid.Invalid.getNullForInvalid( item.getAttributeAsJid("jid"))))) { if (mucOptions.setOnline()) { mXmppConnectionService.getAvatarService().clear(mucOptions); @@ -91,7 +94,8 @@ public class PresenceParser extends AbstractParser implements Consumer<im.conver mXmppConnectionService.databaseBackend.updateConversation( conversation); } - final var modified = current == null || !current.equals(user.getFullJid()); + final var modified = + current == null || !current.equals(user.getFullJid()); mXmppConnectionService.persistSelfNick(user, modified); invokeRenameListener(mucOptions, true); } @@ -166,7 +170,7 @@ public class PresenceParser extends AbstractParser implements Consumer<im.conver final Jid alternate = destroy == null ? null - : InvalidJid.getNullForInvalid( + : Jid.Invalid.getNullForInvalid( destroy.getAttributeAsJid("jid")); mucOptions.setError(MucOptions.Error.DESTROYED); if (alternate != null) { @@ -301,7 +305,9 @@ public class PresenceParser extends AbstractParser implements Consumer<im.conver return codes; } - private void parseContactPresence(final im.conversations.android.xmpp.model.stanza.Presence packet, final Account account) { + private void parseContactPresence( + final im.conversations.android.xmpp.model.stanza.Presence packet, + final Account account) { final PresenceGenerator mPresenceGenerator = mXmppConnectionService.getPresenceGenerator(); final Jid from = packet.getFrom(); if (from == null || from.equals(account.getJid())) { diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 9e3fd1e40..4982a4463 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -29,7 +29,6 @@ import eu.siacs.conversations.utils.CursorUtils; import eu.siacs.conversations.utils.FtsUtils; import eu.siacs.conversations.utils.MimeUtils; import eu.siacs.conversations.utils.Resolver; -import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.mam.MamReference; import java.io.ByteArrayInputStream; @@ -1142,7 +1141,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { cursor.getString(cursor.getColumnIndex(Account.SERVER)), null) .getDomain() - .toEscapedString(); + .toString(); } catch (IllegalArgumentException ignored) { Log.e( Config.LOGTAG, @@ -1307,7 +1306,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { selectionArgs); while (cursor.moveToNext()) { final Conversation conversation = Conversation.fromCursor(cursor); - if (conversation.getJid() instanceof InvalidJid) { + if (conversation.getJid() instanceof Jid.Invalid) { continue; } list.add(conversation); @@ -1616,7 +1615,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { } cursor.moveToFirst(); final Conversation conversation = Conversation.fromCursor(cursor); - if (conversation.getJid() instanceof InvalidJid) { + if (conversation.getJid() instanceof Jid.Invalid) { return null; } return conversation; @@ -1649,7 +1648,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { } cursor.moveToFirst(); final Conversation conversation = Conversation.fromCursor(cursor); - if (conversation.getJid() instanceof InvalidJid) { + if (conversation.getJid() instanceof Jid.Invalid) { return null; } conversation.setAccount(account); diff --git a/src/main/java/eu/siacs/conversations/persistance/UnifiedPushDatabase.java b/src/main/java/eu/siacs/conversations/persistance/UnifiedPushDatabase.java index 6535a5c80..4d7dc7a25 100644 --- a/src/main/java/eu/siacs/conversations/persistance/UnifiedPushDatabase.java +++ b/src/main/java/eu/siacs/conversations/persistance/UnifiedPushDatabase.java @@ -6,19 +6,15 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.common.base.MoreObjects; import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; - -import java.util.List; - import eu.siacs.conversations.Config; import eu.siacs.conversations.services.UnifiedPushBroker; +import java.util.List; public class UnifiedPushDatabase extends SQLiteOpenHelper { private static final String DATABASE_NAME = "unified-push-distributor"; @@ -42,7 +38,9 @@ public class UnifiedPushDatabase extends SQLiteOpenHelper { @Override public void onCreate(final SQLiteDatabase sqLiteDatabase) { sqLiteDatabase.execSQL( - "CREATE TABLE push (account TEXT, transport TEXT, application TEXT NOT NULL, instance TEXT NOT NULL UNIQUE, endpoint TEXT, expiration NUMBER DEFAULT 0)"); + "CREATE TABLE push (account TEXT, transport TEXT, application TEXT NOT NULL," + + " instance TEXT NOT NULL UNIQUE, endpoint TEXT, expiration NUMBER DEFAULT" + + " 0)"); } public boolean register(final String application, final String instance) { @@ -113,7 +111,8 @@ public class UnifiedPushDatabase extends SQLiteOpenHelper { sqLiteDatabase.query( "push", new String[] {"application", "endpoint"}, - "account = ? AND transport = ? AND instance = ? AND endpoint IS NOT NULL AND expiration >= " + "account = ? AND transport = ? AND instance = ? AND endpoint IS NOT NULL" + + " AND expiration >= " + expiration, new String[] {account, transport, instance}, null, @@ -131,17 +130,26 @@ public class UnifiedPushDatabase extends SQLiteOpenHelper { public List<PushTarget> deletePushTargets() { final SQLiteDatabase sqLiteDatabase = getReadableDatabase(); final ImmutableList.Builder<PushTarget> builder = new ImmutableList.Builder<>(); - try (final Cursor cursor = sqLiteDatabase.query("push",new String[]{"application","instance"},null,null,null,null,null)) { + try (final Cursor cursor = + sqLiteDatabase.query( + "push", + new String[] {"application", "instance"}, + null, + null, + null, + null, + null)) { if (cursor != null && cursor.moveToFirst()) { - builder.add(new PushTarget( - cursor.getString(cursor.getColumnIndexOrThrow("application")), - cursor.getString(cursor.getColumnIndexOrThrow("instance")))); + builder.add( + new PushTarget( + cursor.getString(cursor.getColumnIndexOrThrow("application")), + cursor.getString(cursor.getColumnIndexOrThrow("instance")))); } } catch (final Exception e) { - Log.d(Config.LOGTAG,"unable to retrieve push targets",e); + Log.d(Config.LOGTAG, "unable to retrieve push targets", e); return builder.build(); } - sqLiteDatabase.delete("push",null,null); + sqLiteDatabase.delete("push", null, null); return builder.build(); } @@ -149,9 +157,10 @@ public class UnifiedPushDatabase extends SQLiteOpenHelper { final SQLiteDatabase sqLiteDatabase = getReadableDatabase(); try (final Cursor cursor = sqLiteDatabase.rawQuery( - "SELECT EXISTS(SELECT endpoint FROM push WHERE account = ? AND transport = ?)", + "SELECT EXISTS(SELECT endpoint FROM push WHERE account = ? AND transport =" + + " ?)", new String[] { - transport.account.getUuid(), transport.transport.toEscapedString() + transport.account.getUuid(), transport.transport.toString() })) { if (cursor != null && cursor.moveToFirst()) { return cursor.getInt(0) > 0; diff --git a/src/main/java/eu/siacs/conversations/services/AvatarService.java b/src/main/java/eu/siacs/conversations/services/AvatarService.java index e9e827c56..460215576 100644 --- a/src/main/java/eu/siacs/conversations/services/AvatarService.java +++ b/src/main/java/eu/siacs/conversations/services/AvatarService.java @@ -16,17 +16,9 @@ import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; import android.util.LruCache; - import androidx.annotation.ColorInt; import androidx.annotation.Nullable; import androidx.core.content.res.ResourcesCompat; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Set; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; @@ -43,647 +35,706 @@ import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; import eu.siacs.conversations.xmpp.XmppConnection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; public class AvatarService implements OnAdvancedStreamFeaturesLoaded { - private static final int FG_COLOR = 0xFFFAFAFA; - private static final int TRANSPARENT = 0x00000000; - private static final int PLACEHOLDER_COLOR = 0xFF202020; - - public static final int SYSTEM_UI_AVATAR_SIZE = 48; - - private static final String PREFIX_CONTACT = "contact"; - private static final String PREFIX_CONVERSATION = "conversation"; - private static final String PREFIX_ACCOUNT = "account"; - private static final String PREFIX_GENERIC = "generic"; - - private static final String CHANNEL_SYMBOL = "#"; - - final private Set<Integer> sizes = new HashSet<>(); - final private HashMap<String, Set<String>> conversationDependentKeys = new HashMap<>(); - - protected XmppConnectionService mXmppConnectionService = null; - - AvatarService(XmppConnectionService service) { - this.mXmppConnectionService = service; - } - - public static int getSystemUiAvatarSize(final Context context) { - return (int) (SYSTEM_UI_AVATAR_SIZE * context.getResources().getDisplayMetrics().density); - } - - public Bitmap get(final Avatarable avatarable, final int size, final boolean cachedOnly) { - if (avatarable instanceof Account) { - return get((Account) avatarable,size,cachedOnly); - } else if (avatarable instanceof Conversation) { - return get((Conversation) avatarable, size, cachedOnly); - } else if (avatarable instanceof Message) { - return get((Message) avatarable, size, cachedOnly); - } else if (avatarable instanceof ListItem) { - return get((ListItem) avatarable, size, cachedOnly); - } else if (avatarable instanceof MucOptions.User) { - return get((MucOptions.User) avatarable, size, cachedOnly); - } else if (avatarable instanceof Room) { - return get((Room) avatarable, size, cachedOnly); - } - throw new AssertionError("AvatarService does not know how to generate avatar from "+avatarable.getClass().getName()); - - } - - private Bitmap get(final Room result, final int size, boolean cacheOnly) { - final Jid room = result.getRoom(); - Conversation conversation = room != null ? mXmppConnectionService.findFirstMuc(room) : null; - if (conversation != null) { - return get(conversation,size,cacheOnly); - } - return get(CHANNEL_SYMBOL, room != null ? room.asBareJid().toEscapedString() : result.getName(), size, cacheOnly); - } - - private Bitmap get(final Contact contact, final int size, boolean cachedOnly) { - if (contact.isSelf()) { - return get(contact.getAccount(), size, cachedOnly); - } - final String KEY = key(contact, size); - Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY); - if (avatar != null || cachedOnly) { - return avatar; - } - if (contact.getAvatarFilename() != null && QuickConversationsService.isQuicksy()) { - avatar = mXmppConnectionService.getFileBackend().getAvatar(contact.getAvatarFilename(), size); - } - if (avatar == null && contact.getProfilePhoto() != null) { - avatar = mXmppConnectionService.getFileBackend().cropCenterSquare(Uri.parse(contact.getProfilePhoto()), size); - } - if (avatar == null && contact.getAvatarFilename() != null) { - avatar = mXmppConnectionService.getFileBackend().getAvatar(contact.getAvatarFilename(), size); - } - if (avatar == null) { - avatar = get(contact.getDisplayName(), contact.getJid().asBareJid().toString(), size, false); - } - this.mXmppConnectionService.getBitmapCache().put(KEY, avatar); - return avatar; - } - - public Bitmap getRoundedShortcut(final MucOptions mucOptions) { - final DisplayMetrics metrics = mXmppConnectionService.getResources().getDisplayMetrics(); - final int size = Math.round(metrics.density * 48); - final Bitmap bitmap = get(mucOptions, size, false); - final Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(output); - final Paint paint = new Paint(); - drawAvatar(bitmap, canvas, paint); - return output; - } - - public Bitmap getRoundedShortcut(final Contact contact) { - return getRoundedShortcut(contact, false); - } - - public Bitmap getRoundedShortcutWithIcon(final Contact contact) { - return getRoundedShortcut(contact, true); - } - - private Bitmap getRoundedShortcut(final Contact contact, boolean withIcon) { - DisplayMetrics metrics = mXmppConnectionService.getResources().getDisplayMetrics(); - int size = Math.round(metrics.density * 48); - Bitmap bitmap = get(contact, size); - Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(output); - final Paint paint = new Paint(); - - drawAvatar(bitmap, canvas, paint); - if (withIcon) { - drawIcon(canvas, paint); - } - return output; - } - - private static void drawAvatar(Bitmap bitmap, Canvas canvas, Paint paint) { - final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); - paint.setAntiAlias(true); - canvas.drawARGB(0, 0, 0, 0); - canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2, bitmap.getWidth() / 2, paint); - paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); - canvas.drawBitmap(bitmap, rect, rect, paint); - } - - private void drawIcon(Canvas canvas, Paint paint) { - final Resources resources = mXmppConnectionService.getResources(); - final Bitmap icon = getRoundLauncherIcon(resources); - if (icon == null) { - return; - } - paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)); - - int iconSize = Math.round(canvas.getHeight() / 2.6f); - - int left = canvas.getWidth() - iconSize; - int top = canvas.getHeight() - iconSize; - final Rect rect = new Rect(left, top, left + iconSize, top + iconSize); - canvas.drawBitmap(icon, null, rect, paint); - } - - private static Bitmap getRoundLauncherIcon(Resources resources) { - - final Drawable drawable = ResourcesCompat.getDrawable(resources, R.mipmap.new_launcher_round,null); - if (drawable == null) { - return null; - } - - if (drawable instanceof BitmapDrawable) { - return ((BitmapDrawable)drawable).getBitmap(); - } - - Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - drawable.draw(canvas); - - return bitmap; - } - - public Bitmap get(final MucOptions.User user, final int size, boolean cachedOnly) { - Contact c = user.getContact(); - if (c != null && (c.getProfilePhoto() != null || c.getAvatarFilename() != null || user.getAvatar() == null)) { - return get(c, size, cachedOnly); - } else { - return getImpl(user, size, cachedOnly); - } - } - - private Bitmap getImpl(final MucOptions.User user, final int size, boolean cachedOnly) { - final String KEY = key(user, size); - Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY); - if (avatar != null || cachedOnly) { - return avatar; - } - if (user.getAvatar() != null) { - avatar = mXmppConnectionService.getFileBackend().getAvatar(user.getAvatar(), size); - } - if (avatar == null) { - Contact contact = user.getContact(); - if (contact != null) { - avatar = get(contact, size, false); - } else { - String seed = user.getRealJid() != null ? user.getRealJid().asBareJid().toString() : null; - avatar = get(user.getName(), seed, size, false); - } - } - this.mXmppConnectionService.getBitmapCache().put(KEY, avatar); - return avatar; - } - - public void clear(Contact contact) { - synchronized (this.sizes) { - for (final Integer size : sizes) { - this.mXmppConnectionService.getBitmapCache().remove(key(contact, size)); - } - } - for (Conversation conversation : mXmppConnectionService.findAllConferencesWith(contact)) { - MucOptions.User user = conversation.getMucOptions().findUserByRealJid(contact.getJid().asBareJid()); - if (user != null) { - clear(user); - } - clear(conversation); - } - } - - private String key(Contact contact, int size) { - synchronized (this.sizes) { - this.sizes.add(size); - } - return PREFIX_CONTACT + - '\0' + - contact.getAccount().getJid().asBareJid() + - '\0' + - emptyOnNull(contact.getJid()) + - '\0' + - size; - } - - private String key(MucOptions.User user, int size) { - synchronized (this.sizes) { - this.sizes.add(size); - } - return PREFIX_CONTACT + - '\0' + - user.getAccount().getJid().asBareJid() + - '\0' + - emptyOnNull(user.getFullJid()) + - '\0' + - emptyOnNull(user.getRealJid()) + - '\0' + - size; - } - - public Bitmap get(ListItem item, int size) { - return get(item, size, false); - } - - public Bitmap get(ListItem item, int size, boolean cachedOnly) { - if (item instanceof RawBlockable) { - return get(item.getDisplayName(), item.getJid().toEscapedString(), size, cachedOnly); - } else if (item instanceof Contact) { - return get((Contact) item, size, cachedOnly); - } else if (item instanceof Bookmark) { - Bookmark bookmark = (Bookmark) item; - if (bookmark.getConversation() != null) { - return get(bookmark.getConversation(), size, cachedOnly); - } else { - Jid jid = bookmark.getJid(); - Account account = bookmark.getAccount(); - Contact contact = jid == null ? null : account.getRoster().getContact(jid); - if (contact != null && contact.getAvatarFilename() != null) { - return get(contact, size, cachedOnly); - } - String seed = jid != null ? jid.asBareJid().toString() : null; - return get(bookmark.getDisplayName(), seed, size, cachedOnly); - } - } else { - String seed = item.getJid() != null ? item.getJid().asBareJid().toString() : null; - return get(item.getDisplayName(), seed, size, cachedOnly); - } - } - - public Bitmap get(Conversation conversation, int size) { - return get(conversation, size, false); - } - - public Bitmap get(Conversation conversation, int size, boolean cachedOnly) { - if (conversation.getMode() == Conversation.MODE_SINGLE) { - return get(conversation.getContact(), size, cachedOnly); - } else { - return get(conversation.getMucOptions(), size, cachedOnly); - } - } - - public void clear(Conversation conversation) { - if (conversation.getMode() == Conversation.MODE_SINGLE) { - clear(conversation.getContact()); - } else { - clear(conversation.getMucOptions()); - synchronized (this.conversationDependentKeys) { - Set<String> keys = this.conversationDependentKeys.get(conversation.getUuid()); - if (keys == null) { - return; - } - LruCache<String, Bitmap> cache = this.mXmppConnectionService.getBitmapCache(); - for (String key : keys) { - cache.remove(key); - } - keys.clear(); - } - } - } - - private Bitmap get(MucOptions mucOptions, int size, boolean cachedOnly) { - final String KEY = key(mucOptions, size); - Bitmap bitmap = this.mXmppConnectionService.getBitmapCache().get(KEY); - if (bitmap != null || cachedOnly) { - return bitmap; - } - - bitmap = mXmppConnectionService.getFileBackend().getAvatar(mucOptions.getAvatar(), size); - - if (bitmap == null) { - Conversation c = mucOptions.getConversation(); - if (mucOptions.isPrivateAndNonAnonymous()) { - final List<MucOptions.User> users = mucOptions.getUsersRelevantForNameAndAvatar(); - if (users.size() == 0) { - bitmap = getImpl(c.getName().toString(), c.getJid().asBareJid().toString(), size); - } else { - bitmap = getImpl(users, size); - } - } else { - bitmap = getImpl(CHANNEL_SYMBOL, c.getJid().asBareJid().toString(), size); - } - } - - this.mXmppConnectionService.getBitmapCache().put(KEY, bitmap); - - return bitmap; - } - - private Bitmap get(List<MucOptions.User> users, int size, boolean cachedOnly) { - final String KEY = key(users, size); - Bitmap bitmap = this.mXmppConnectionService.getBitmapCache().get(KEY); - if (bitmap != null || cachedOnly) { - return bitmap; - } - bitmap = getImpl(users, size); - this.mXmppConnectionService.getBitmapCache().put(KEY, bitmap); - return bitmap; - } - - private Bitmap getImpl(List<MucOptions.User> users, int size) { - int count = users.size(); - Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - bitmap.eraseColor(TRANSPARENT); - if (count == 0) { - throw new AssertionError("Unable to draw tiles for 0 users"); - } else if (count == 1) { - drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size); - drawTile(canvas, users.get(0).getAccount(), size / 2 + 1, 0, size, size); - } else if (count == 2) { - drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size); - drawTile(canvas, users.get(1), size / 2 + 1, 0, size, size); - } else if (count == 3) { - drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size); - drawTile(canvas, users.get(1), size / 2 + 1, 0, size, size / 2 - 1); - drawTile(canvas, users.get(2), size / 2 + 1, size / 2 + 1, size, - size); - } else if (count == 4) { - drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size / 2 - 1); - drawTile(canvas, users.get(1), 0, size / 2 + 1, size / 2 - 1, size); - drawTile(canvas, users.get(2), size / 2 + 1, 0, size, size / 2 - 1); - drawTile(canvas, users.get(3), size / 2 + 1, size / 2 + 1, size, - size); - } else { - drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size / 2 - 1); - drawTile(canvas, users.get(1), 0, size / 2 + 1, size / 2 - 1, size); - drawTile(canvas, users.get(2), size / 2 + 1, 0, size, size / 2 - 1); - drawTile(canvas, "\u2026", PLACEHOLDER_COLOR, size / 2 + 1, size / 2 + 1, - size, size); - } - return bitmap; - } - - public void clear(final MucOptions options) { - if (options == null) { - return; - } - synchronized (this.sizes) { - for (Integer size : sizes) { - this.mXmppConnectionService.getBitmapCache().remove(key(options, size)); - } - } - } - - private String key(final MucOptions options, int size) { - synchronized (this.sizes) { - this.sizes.add(size); - } - return PREFIX_CONVERSATION + "_" + options.getConversation().getUuid() + "_" + size; - } - - private String key(List<MucOptions.User> users, int size) { - final Conversation conversation = users.get(0).getConversation(); - StringBuilder builder = new StringBuilder("TILE_"); - builder.append(conversation.getUuid()); - - for (MucOptions.User user : users) { - builder.append("\0"); - builder.append(emptyOnNull(user.getRealJid())); - builder.append("\0"); - builder.append(emptyOnNull(user.getFullJid())); - } - builder.append('\0'); - builder.append(size); - final String key = builder.toString(); - synchronized (this.conversationDependentKeys) { - Set<String> keys; - if (this.conversationDependentKeys.containsKey(conversation.getUuid())) { - keys = this.conversationDependentKeys.get(conversation.getUuid()); - } else { - keys = new HashSet<>(); - this.conversationDependentKeys.put(conversation.getUuid(), keys); - } - keys.add(key); - } - return key; - } - - public Bitmap get(Account account, int size) { - return get(account, size, false); - } - - public Bitmap get(Account account, int size, boolean cachedOnly) { - final String KEY = key(account, size); - Bitmap avatar = mXmppConnectionService.getBitmapCache().get(KEY); - if (avatar != null || cachedOnly) { - return avatar; - } - avatar = mXmppConnectionService.getFileBackend().getAvatar(account.getAvatar(), size); - if (avatar == null) { - final String displayName = account.getDisplayName(); - final String jid = account.getJid().asBareJid().toEscapedString(); - if (QuickConversationsService.isQuicksy() && !TextUtils.isEmpty(displayName)) { - avatar = get(displayName, jid, size, false); - } else { - avatar = get(jid, null, size, false); - } - } - mXmppConnectionService.getBitmapCache().put(KEY, avatar); - return avatar; - } - - public Bitmap get(Message message, int size, boolean cachedOnly) { - final Conversational conversation = message.getConversation(); - if (message.getType() == Message.TYPE_STATUS && message.getCounterparts() != null && message.getCounterparts().size() > 1) { - return get(message.getCounterparts(), size, cachedOnly); - } else if (message.getStatus() == Message.STATUS_RECEIVED) { - Contact c = message.getContact(); - if (c != null && (c.getProfilePhoto() != null || c.getAvatarFilename() != null)) { - return get(c, size, cachedOnly); - } else if (conversation instanceof Conversation && message.getConversation().getMode() == Conversation.MODE_MULTI) { - final Jid trueCounterpart = message.getTrueCounterpart(); - final MucOptions mucOptions = ((Conversation) conversation).getMucOptions(); - MucOptions.User user; - if (trueCounterpart != null) { - user = mucOptions.findOrCreateUserByRealJid(trueCounterpart, message.getCounterpart()); - } else { - user = mucOptions.findUserByFullJid(message.getCounterpart()); - } - if (user != null) { - return getImpl(user, size, cachedOnly); - } - } else if (c != null) { - return get(c, size, cachedOnly); - } - Jid tcp = message.getTrueCounterpart(); - String seed = tcp != null ? tcp.asBareJid().toString() : null; - return get(UIHelper.getMessageDisplayName(message), seed, size, cachedOnly); - } else { - return get(conversation.getAccount(), size, cachedOnly); - } - } - - public void clear(Account account) { - synchronized (this.sizes) { - for (Integer size : sizes) { - this.mXmppConnectionService.getBitmapCache().remove(key(account, size)); - } - } - } - - public void clear(MucOptions.User user) { - synchronized (this.sizes) { - for (Integer size : sizes) { - this.mXmppConnectionService.getBitmapCache().remove(key(user, size)); - } - } - } - - private String key(Account account, int size) { - synchronized (this.sizes) { - this.sizes.add(size); - } - return PREFIX_ACCOUNT + "_" + account.getUuid() + "_" - + size; - } - - /*public Bitmap get(String name, int size) { - return get(name,null, size,false); - }*/ - - public Bitmap get(final String name, String seed, final int size, boolean cachedOnly) { - final String KEY = key(seed == null ? name : name+"\0"+seed, size); - Bitmap bitmap = mXmppConnectionService.getBitmapCache().get(KEY); - if (bitmap != null || cachedOnly) { - return bitmap; - } - bitmap = getImpl(name, seed, size); - mXmppConnectionService.getBitmapCache().put(KEY, bitmap); - return bitmap; - } - - public static Bitmap get(final Jid jid, final int size) { - return getImpl(jid.asBareJid().toEscapedString(), null, size); - } - - private static Bitmap getImpl(final String name, final String seed, final int size) { - Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - final String trimmedName = name == null ? "" : name.trim(); - drawTile(canvas, trimmedName, seed, 0, 0, size, size); - return bitmap; - } - - private String key(String name, int size) { - synchronized (this.sizes) { - this.sizes.add(size); - } - return PREFIX_GENERIC + "_" + name + "_" + size; - } - - private static boolean drawTile(Canvas canvas, String letter, int tileColor, int left, int top, int right, int bottom) { - letter = letter.toUpperCase(Locale.getDefault()); - Paint tilePaint = new Paint(), textPaint = new Paint(); - tilePaint.setColor(tileColor); - textPaint.setFlags(Paint.ANTI_ALIAS_FLAG); - textPaint.setColor(FG_COLOR); - textPaint.setTypeface(Typeface.create("sans-serif-light", - Typeface.NORMAL)); - textPaint.setTextSize((float) ((right - left) * 0.8)); - Rect rect = new Rect(); - - canvas.drawRect(new Rect(left, top, right, bottom), tilePaint); - textPaint.getTextBounds(letter, 0, 1, rect); - float width = textPaint.measureText(letter); - canvas.drawText(letter, (right + left) / 2 - width / 2, (top + bottom) - / 2 + rect.height() / 2, textPaint); - return true; - } - - private boolean drawTile(Canvas canvas, MucOptions.User user, int left, int top, int right, int bottom) { - Contact contact = user.getContact(); - if (contact != null) { - Uri uri = null; - if (contact.getAvatarFilename() != null && QuickConversationsService.isQuicksy()) { - uri = mXmppConnectionService.getFileBackend().getAvatarUri(contact.getAvatarFilename()); - } else if (contact.getProfilePhoto() != null) { - uri = Uri.parse(contact.getProfilePhoto()); - } else if (contact.getAvatarFilename() != null) { - uri = mXmppConnectionService.getFileBackend().getAvatarUri(contact.getAvatarFilename()); - } - if (drawTile(canvas, uri, left, top, right, bottom)) { - return true; - } - } else if (user.getAvatar() != null) { - Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(user.getAvatar()); - if (drawTile(canvas, uri, left, top, right, bottom)) { - return true; - } - } - if (contact != null) { - String seed = contact.getJid().asBareJid().toString(); - drawTile(canvas, contact.getDisplayName(), seed, left, top, right, bottom); - } else { - String seed = user.getRealJid() == null ? null : user.getRealJid().asBareJid().toString(); - drawTile(canvas, user.getName(), seed, left, top, right, bottom); - } - return true; - } - - private boolean drawTile(Canvas canvas, Account account, int left, int top, int right, int bottom) { - String avatar = account.getAvatar(); - if (avatar != null) { - Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(avatar); - if (uri != null) { - if (drawTile(canvas, uri, left, top, right, bottom)) { - return true; - } - } - } - String name = account.getJid().asBareJid().toString(); - return drawTile(canvas, name, name, left, top, right, bottom); - } - - private static boolean drawTile(Canvas canvas, String name, String seed, int left, int top, int right, int bottom) { - if (name != null) { - final String letter = name.equals(CHANNEL_SYMBOL) ? name : getFirstLetter(name); - final int color = UIHelper.getColorForName(seed == null ? name : seed); - drawTile(canvas, letter, color, left, top, right, bottom); - return true; - } - return false; - } - - private static String getFirstLetter(String name) { - for (Character c : name.toCharArray()) { - if (Character.isLetterOrDigit(c)) { - return c.toString(); - } - } - return "X"; - } - - private boolean drawTile(Canvas canvas, Uri uri, int left, int top, int right, int bottom) { - if (uri != null) { - Bitmap bitmap = mXmppConnectionService.getFileBackend() - .cropCenter(uri, bottom - top, right - left); - if (bitmap != null) { - drawTile(canvas, bitmap, left, top, right, bottom); - return true; - } - } - return false; - } - - private boolean drawTile(Canvas canvas, Bitmap bm, int dstleft, int dsttop, int dstright, int dstbottom) { - Rect dst = new Rect(dstleft, dsttop, dstright, dstbottom); - canvas.drawBitmap(bm, null, dst, null); - return true; - } - - @Override - public void onAdvancedStreamFeaturesAvailable(Account account) { - XmppConnection.Features features = account.getXmppConnection().getFeatures(); - if (features.pep() && !features.pepPersistent()) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": has pep but is not persistent"); - if (account.getAvatar() != null) { - mXmppConnectionService.republishAvatarIfNeeded(account); - } - } - } - - private static String emptyOnNull(@Nullable Jid value) { - return value == null ? "" : value.toString(); - } - - public interface Avatarable { - @ColorInt int getAvatarBackgroundColor(); - String getAvatarName(); - } + private static final int FG_COLOR = 0xFFFAFAFA; + private static final int TRANSPARENT = 0x00000000; + private static final int PLACEHOLDER_COLOR = 0xFF202020; + + public static final int SYSTEM_UI_AVATAR_SIZE = 48; + + private static final String PREFIX_CONTACT = "contact"; + private static final String PREFIX_CONVERSATION = "conversation"; + private static final String PREFIX_ACCOUNT = "account"; + private static final String PREFIX_GENERIC = "generic"; + + private static final String CHANNEL_SYMBOL = "#"; + + private final Set<Integer> sizes = new HashSet<>(); + private final HashMap<String, Set<String>> conversationDependentKeys = new HashMap<>(); + + protected XmppConnectionService mXmppConnectionService = null; + + AvatarService(XmppConnectionService service) { + this.mXmppConnectionService = service; + } + + public static int getSystemUiAvatarSize(final Context context) { + return (int) (SYSTEM_UI_AVATAR_SIZE * context.getResources().getDisplayMetrics().density); + } + + public Bitmap get(final Avatarable avatarable, final int size, final boolean cachedOnly) { + if (avatarable instanceof Account) { + return get((Account) avatarable, size, cachedOnly); + } else if (avatarable instanceof Conversation) { + return get((Conversation) avatarable, size, cachedOnly); + } else if (avatarable instanceof Message) { + return get((Message) avatarable, size, cachedOnly); + } else if (avatarable instanceof ListItem) { + return get((ListItem) avatarable, size, cachedOnly); + } else if (avatarable instanceof MucOptions.User) { + return get((MucOptions.User) avatarable, size, cachedOnly); + } else if (avatarable instanceof Room) { + return get((Room) avatarable, size, cachedOnly); + } + throw new AssertionError( + "AvatarService does not know how to generate avatar from " + + avatarable.getClass().getName()); + } + + private Bitmap get(final Room result, final int size, boolean cacheOnly) { + final Jid room = result.getRoom(); + Conversation conversation = room != null ? mXmppConnectionService.findFirstMuc(room) : null; + if (conversation != null) { + return get(conversation, size, cacheOnly); + } + return get( + CHANNEL_SYMBOL, + room != null ? room.asBareJid().toString() : result.getName(), + size, + cacheOnly); + } + + private Bitmap get(final Contact contact, final int size, boolean cachedOnly) { + if (contact.isSelf()) { + return get(contact.getAccount(), size, cachedOnly); + } + final String KEY = key(contact, size); + Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY); + if (avatar != null || cachedOnly) { + return avatar; + } + if (contact.getAvatarFilename() != null && QuickConversationsService.isQuicksy()) { + avatar = + mXmppConnectionService + .getFileBackend() + .getAvatar(contact.getAvatarFilename(), size); + } + if (avatar == null && contact.getProfilePhoto() != null) { + avatar = + mXmppConnectionService + .getFileBackend() + .cropCenterSquare(Uri.parse(contact.getProfilePhoto()), size); + } + if (avatar == null && contact.getAvatarFilename() != null) { + avatar = + mXmppConnectionService + .getFileBackend() + .getAvatar(contact.getAvatarFilename(), size); + } + if (avatar == null) { + avatar = + get( + contact.getDisplayName(), + contact.getJid().asBareJid().toString(), + size, + false); + } + this.mXmppConnectionService.getBitmapCache().put(KEY, avatar); + return avatar; + } + + public Bitmap getRoundedShortcut(final MucOptions mucOptions) { + final DisplayMetrics metrics = mXmppConnectionService.getResources().getDisplayMetrics(); + final int size = Math.round(metrics.density * 48); + final Bitmap bitmap = get(mucOptions, size, false); + final Bitmap output = + Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(output); + final Paint paint = new Paint(); + drawAvatar(bitmap, canvas, paint); + return output; + } + + public Bitmap getRoundedShortcut(final Contact contact) { + return getRoundedShortcut(contact, false); + } + + public Bitmap getRoundedShortcutWithIcon(final Contact contact) { + return getRoundedShortcut(contact, true); + } + + private Bitmap getRoundedShortcut(final Contact contact, boolean withIcon) { + DisplayMetrics metrics = mXmppConnectionService.getResources().getDisplayMetrics(); + int size = Math.round(metrics.density * 48); + Bitmap bitmap = get(contact, size); + Bitmap output = + Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(output); + final Paint paint = new Paint(); + + drawAvatar(bitmap, canvas, paint); + if (withIcon) { + drawIcon(canvas, paint); + } + return output; + } + + private static void drawAvatar(Bitmap bitmap, Canvas canvas, Paint paint) { + final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); + paint.setAntiAlias(true); + canvas.drawARGB(0, 0, 0, 0); + canvas.drawCircle( + bitmap.getWidth() / 2, bitmap.getHeight() / 2, bitmap.getWidth() / 2, paint); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); + canvas.drawBitmap(bitmap, rect, rect, paint); + } + + private void drawIcon(Canvas canvas, Paint paint) { + final Resources resources = mXmppConnectionService.getResources(); + final Bitmap icon = getRoundLauncherIcon(resources); + if (icon == null) { + return; + } + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)); + + int iconSize = Math.round(canvas.getHeight() / 2.6f); + + int left = canvas.getWidth() - iconSize; + int top = canvas.getHeight() - iconSize; + final Rect rect = new Rect(left, top, left + iconSize, top + iconSize); + canvas.drawBitmap(icon, null, rect, paint); + } + + private static Bitmap getRoundLauncherIcon(Resources resources) { + + final Drawable drawable = + ResourcesCompat.getDrawable(resources, R.mipmap.new_launcher_round, null); + if (drawable == null) { + return null; + } + + if (drawable instanceof BitmapDrawable) { + return ((BitmapDrawable) drawable).getBitmap(); + } + + Bitmap bitmap = + Bitmap.createBitmap( + drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight(), + Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + + return bitmap; + } + + public Bitmap get(final MucOptions.User user, final int size, boolean cachedOnly) { + Contact c = user.getContact(); + if (c != null + && (c.getProfilePhoto() != null + || c.getAvatarFilename() != null + || user.getAvatar() == null)) { + return get(c, size, cachedOnly); + } else { + return getImpl(user, size, cachedOnly); + } + } + + private Bitmap getImpl(final MucOptions.User user, final int size, boolean cachedOnly) { + final String KEY = key(user, size); + Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY); + if (avatar != null || cachedOnly) { + return avatar; + } + if (user.getAvatar() != null) { + avatar = mXmppConnectionService.getFileBackend().getAvatar(user.getAvatar(), size); + } + if (avatar == null) { + Contact contact = user.getContact(); + if (contact != null) { + avatar = get(contact, size, false); + } else { + String seed = + user.getRealJid() != null ? user.getRealJid().asBareJid().toString() : null; + avatar = get(user.getName(), seed, size, false); + } + } + this.mXmppConnectionService.getBitmapCache().put(KEY, avatar); + return avatar; + } + + public void clear(Contact contact) { + synchronized (this.sizes) { + for (final Integer size : sizes) { + this.mXmppConnectionService.getBitmapCache().remove(key(contact, size)); + } + } + for (Conversation conversation : mXmppConnectionService.findAllConferencesWith(contact)) { + MucOptions.User user = + conversation.getMucOptions().findUserByRealJid(contact.getJid().asBareJid()); + if (user != null) { + clear(user); + } + clear(conversation); + } + } + + private String key(Contact contact, int size) { + synchronized (this.sizes) { + this.sizes.add(size); + } + return PREFIX_CONTACT + + '\0' + + contact.getAccount().getJid().asBareJid() + + '\0' + + emptyOnNull(contact.getJid()) + + '\0' + + size; + } + + private String key(MucOptions.User user, int size) { + synchronized (this.sizes) { + this.sizes.add(size); + } + return PREFIX_CONTACT + + '\0' + + user.getAccount().getJid().asBareJid() + + '\0' + + emptyOnNull(user.getFullJid()) + + '\0' + + emptyOnNull(user.getRealJid()) + + '\0' + + size; + } + + public Bitmap get(ListItem item, int size) { + return get(item, size, false); + } + + public Bitmap get(ListItem item, int size, boolean cachedOnly) { + if (item instanceof RawBlockable) { + return get(item.getDisplayName(), item.getJid().toString(), size, cachedOnly); + } else if (item instanceof Contact) { + return get((Contact) item, size, cachedOnly); + } else if (item instanceof Bookmark bookmark) { + if (bookmark.getConversation() != null) { + return get(bookmark.getConversation(), size, cachedOnly); + } else { + Jid jid = bookmark.getJid(); + Account account = bookmark.getAccount(); + Contact contact = jid == null ? null : account.getRoster().getContact(jid); + if (contact != null && contact.getAvatarFilename() != null) { + return get(contact, size, cachedOnly); + } + String seed = jid != null ? jid.asBareJid().toString() : null; + return get(bookmark.getDisplayName(), seed, size, cachedOnly); + } + } else { + String seed = item.getJid() != null ? item.getJid().asBareJid().toString() : null; + return get(item.getDisplayName(), seed, size, cachedOnly); + } + } + + public Bitmap get(Conversation conversation, int size) { + return get(conversation, size, false); + } + + public Bitmap get(Conversation conversation, int size, boolean cachedOnly) { + if (conversation.getMode() == Conversation.MODE_SINGLE) { + return get(conversation.getContact(), size, cachedOnly); + } else { + return get(conversation.getMucOptions(), size, cachedOnly); + } + } + + public void clear(Conversation conversation) { + if (conversation.getMode() == Conversation.MODE_SINGLE) { + clear(conversation.getContact()); + } else { + clear(conversation.getMucOptions()); + synchronized (this.conversationDependentKeys) { + Set<String> keys = this.conversationDependentKeys.get(conversation.getUuid()); + if (keys == null) { + return; + } + LruCache<String, Bitmap> cache = this.mXmppConnectionService.getBitmapCache(); + for (String key : keys) { + cache.remove(key); + } + keys.clear(); + } + } + } + + private Bitmap get(MucOptions mucOptions, int size, boolean cachedOnly) { + final String KEY = key(mucOptions, size); + Bitmap bitmap = this.mXmppConnectionService.getBitmapCache().get(KEY); + if (bitmap != null || cachedOnly) { + return bitmap; + } + + bitmap = mXmppConnectionService.getFileBackend().getAvatar(mucOptions.getAvatar(), size); + + if (bitmap == null) { + Conversation c = mucOptions.getConversation(); + if (mucOptions.isPrivateAndNonAnonymous()) { + final List<MucOptions.User> users = mucOptions.getUsersRelevantForNameAndAvatar(); + if (users.size() == 0) { + bitmap = + getImpl( + c.getName().toString(), + c.getJid().asBareJid().toString(), + size); + } else { + bitmap = getImpl(users, size); + } + } else { + bitmap = getImpl(CHANNEL_SYMBOL, c.getJid().asBareJid().toString(), size); + } + } + + this.mXmppConnectionService.getBitmapCache().put(KEY, bitmap); + + return bitmap; + } + + private Bitmap get(List<MucOptions.User> users, int size, boolean cachedOnly) { + final String KEY = key(users, size); + Bitmap bitmap = this.mXmppConnectionService.getBitmapCache().get(KEY); + if (bitmap != null || cachedOnly) { + return bitmap; + } + bitmap = getImpl(users, size); + this.mXmppConnectionService.getBitmapCache().put(KEY, bitmap); + return bitmap; + } + + private Bitmap getImpl(List<MucOptions.User> users, int size) { + int count = users.size(); + Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + bitmap.eraseColor(TRANSPARENT); + if (count == 0) { + throw new AssertionError("Unable to draw tiles for 0 users"); + } else if (count == 1) { + drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size); + drawTile(canvas, users.get(0).getAccount(), size / 2 + 1, 0, size, size); + } else if (count == 2) { + drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size); + drawTile(canvas, users.get(1), size / 2 + 1, 0, size, size); + } else if (count == 3) { + drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size); + drawTile(canvas, users.get(1), size / 2 + 1, 0, size, size / 2 - 1); + drawTile(canvas, users.get(2), size / 2 + 1, size / 2 + 1, size, size); + } else if (count == 4) { + drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size / 2 - 1); + drawTile(canvas, users.get(1), 0, size / 2 + 1, size / 2 - 1, size); + drawTile(canvas, users.get(2), size / 2 + 1, 0, size, size / 2 - 1); + drawTile(canvas, users.get(3), size / 2 + 1, size / 2 + 1, size, size); + } else { + drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size / 2 - 1); + drawTile(canvas, users.get(1), 0, size / 2 + 1, size / 2 - 1, size); + drawTile(canvas, users.get(2), size / 2 + 1, 0, size, size / 2 - 1); + drawTile(canvas, "\u2026", PLACEHOLDER_COLOR, size / 2 + 1, size / 2 + 1, size, size); + } + return bitmap; + } + + public void clear(final MucOptions options) { + if (options == null) { + return; + } + synchronized (this.sizes) { + for (Integer size : sizes) { + this.mXmppConnectionService.getBitmapCache().remove(key(options, size)); + } + } + } + + private String key(final MucOptions options, int size) { + synchronized (this.sizes) { + this.sizes.add(size); + } + return PREFIX_CONVERSATION + "_" + options.getConversation().getUuid() + "_" + size; + } + + private String key(List<MucOptions.User> users, int size) { + final Conversation conversation = users.get(0).getConversation(); + StringBuilder builder = new StringBuilder("TILE_"); + builder.append(conversation.getUuid()); + + for (MucOptions.User user : users) { + builder.append("\0"); + builder.append(emptyOnNull(user.getRealJid())); + builder.append("\0"); + builder.append(emptyOnNull(user.getFullJid())); + } + builder.append('\0'); + builder.append(size); + final String key = builder.toString(); + synchronized (this.conversationDependentKeys) { + Set<String> keys; + if (this.conversationDependentKeys.containsKey(conversation.getUuid())) { + keys = this.conversationDependentKeys.get(conversation.getUuid()); + } else { + keys = new HashSet<>(); + this.conversationDependentKeys.put(conversation.getUuid(), keys); + } + keys.add(key); + } + return key; + } + + public Bitmap get(Account account, int size) { + return get(account, size, false); + } + + public Bitmap get(Account account, int size, boolean cachedOnly) { + final String KEY = key(account, size); + Bitmap avatar = mXmppConnectionService.getBitmapCache().get(KEY); + if (avatar != null || cachedOnly) { + return avatar; + } + avatar = mXmppConnectionService.getFileBackend().getAvatar(account.getAvatar(), size); + if (avatar == null) { + final String displayName = account.getDisplayName(); + final String jid = account.getJid().asBareJid().toString(); + if (QuickConversationsService.isQuicksy() && !TextUtils.isEmpty(displayName)) { + avatar = get(displayName, jid, size, false); + } else { + avatar = get(jid, null, size, false); + } + } + mXmppConnectionService.getBitmapCache().put(KEY, avatar); + return avatar; + } + + public Bitmap get(Message message, int size, boolean cachedOnly) { + final Conversational conversation = message.getConversation(); + if (message.getType() == Message.TYPE_STATUS + && message.getCounterparts() != null + && message.getCounterparts().size() > 1) { + return get(message.getCounterparts(), size, cachedOnly); + } else if (message.getStatus() == Message.STATUS_RECEIVED) { + Contact c = message.getContact(); + if (c != null && (c.getProfilePhoto() != null || c.getAvatarFilename() != null)) { + return get(c, size, cachedOnly); + } else if (conversation instanceof Conversation + && message.getConversation().getMode() == Conversation.MODE_MULTI) { + final Jid trueCounterpart = message.getTrueCounterpart(); + final MucOptions mucOptions = ((Conversation) conversation).getMucOptions(); + MucOptions.User user; + if (trueCounterpart != null) { + user = + mucOptions.findOrCreateUserByRealJid( + trueCounterpart, message.getCounterpart()); + } else { + user = mucOptions.findUserByFullJid(message.getCounterpart()); + } + if (user != null) { + return getImpl(user, size, cachedOnly); + } + } else if (c != null) { + return get(c, size, cachedOnly); + } + Jid tcp = message.getTrueCounterpart(); + String seed = tcp != null ? tcp.asBareJid().toString() : null; + return get(UIHelper.getMessageDisplayName(message), seed, size, cachedOnly); + } else { + return get(conversation.getAccount(), size, cachedOnly); + } + } + + public void clear(Account account) { + synchronized (this.sizes) { + for (Integer size : sizes) { + this.mXmppConnectionService.getBitmapCache().remove(key(account, size)); + } + } + } + + public void clear(MucOptions.User user) { + synchronized (this.sizes) { + for (Integer size : sizes) { + this.mXmppConnectionService.getBitmapCache().remove(key(user, size)); + } + } + } + + private String key(Account account, int size) { + synchronized (this.sizes) { + this.sizes.add(size); + } + return PREFIX_ACCOUNT + "_" + account.getUuid() + "_" + size; + } + + /*public Bitmap get(String name, int size) { + return get(name,null, size,false); + }*/ + + public Bitmap get(final String name, String seed, final int size, boolean cachedOnly) { + final String KEY = key(seed == null ? name : name + "\0" + seed, size); + Bitmap bitmap = mXmppConnectionService.getBitmapCache().get(KEY); + if (bitmap != null || cachedOnly) { + return bitmap; + } + bitmap = getImpl(name, seed, size); + mXmppConnectionService.getBitmapCache().put(KEY, bitmap); + return bitmap; + } + + public static Bitmap get(final Jid jid, final int size) { + return getImpl(jid.asBareJid().toString(), null, size); + } + + private static Bitmap getImpl(final String name, final String seed, final int size) { + Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + final String trimmedName = name == null ? "" : name.trim(); + drawTile(canvas, trimmedName, seed, 0, 0, size, size); + return bitmap; + } + + private String key(String name, int size) { + synchronized (this.sizes) { + this.sizes.add(size); + } + return PREFIX_GENERIC + "_" + name + "_" + size; + } + + private static boolean drawTile( + Canvas canvas, String letter, int tileColor, int left, int top, int right, int bottom) { + letter = letter.toUpperCase(Locale.getDefault()); + Paint tilePaint = new Paint(), textPaint = new Paint(); + tilePaint.setColor(tileColor); + textPaint.setFlags(Paint.ANTI_ALIAS_FLAG); + textPaint.setColor(FG_COLOR); + textPaint.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL)); + textPaint.setTextSize((float) ((right - left) * 0.8)); + Rect rect = new Rect(); + + canvas.drawRect(new Rect(left, top, right, bottom), tilePaint); + textPaint.getTextBounds(letter, 0, 1, rect); + float width = textPaint.measureText(letter); + canvas.drawText( + letter, + (right + left) / 2 - width / 2, + (top + bottom) / 2 + rect.height() / 2, + textPaint); + return true; + } + + private boolean drawTile( + Canvas canvas, MucOptions.User user, int left, int top, int right, int bottom) { + Contact contact = user.getContact(); + if (contact != null) { + Uri uri = null; + if (contact.getAvatarFilename() != null && QuickConversationsService.isQuicksy()) { + uri = + mXmppConnectionService + .getFileBackend() + .getAvatarUri(contact.getAvatarFilename()); + } else if (contact.getProfilePhoto() != null) { + uri = Uri.parse(contact.getProfilePhoto()); + } else if (contact.getAvatarFilename() != null) { + uri = + mXmppConnectionService + .getFileBackend() + .getAvatarUri(contact.getAvatarFilename()); + } + if (drawTile(canvas, uri, left, top, right, bottom)) { + return true; + } + } else if (user.getAvatar() != null) { + Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(user.getAvatar()); + if (drawTile(canvas, uri, left, top, right, bottom)) { + return true; + } + } + if (contact != null) { + String seed = contact.getJid().asBareJid().toString(); + drawTile(canvas, contact.getDisplayName(), seed, left, top, right, bottom); + } else { + String seed = + user.getRealJid() == null ? null : user.getRealJid().asBareJid().toString(); + drawTile(canvas, user.getName(), seed, left, top, right, bottom); + } + return true; + } + + private boolean drawTile( + Canvas canvas, Account account, int left, int top, int right, int bottom) { + String avatar = account.getAvatar(); + if (avatar != null) { + Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(avatar); + if (uri != null) { + if (drawTile(canvas, uri, left, top, right, bottom)) { + return true; + } + } + } + String name = account.getJid().asBareJid().toString(); + return drawTile(canvas, name, name, left, top, right, bottom); + } + + private static boolean drawTile( + Canvas canvas, String name, String seed, int left, int top, int right, int bottom) { + if (name != null) { + final String letter = name.equals(CHANNEL_SYMBOL) ? name : getFirstLetter(name); + final int color = UIHelper.getColorForName(seed == null ? name : seed); + drawTile(canvas, letter, color, left, top, right, bottom); + return true; + } + return false; + } + + private static String getFirstLetter(String name) { + for (Character c : name.toCharArray()) { + if (Character.isLetterOrDigit(c)) { + return c.toString(); + } + } + return "X"; + } + + private boolean drawTile(Canvas canvas, Uri uri, int left, int top, int right, int bottom) { + if (uri != null) { + Bitmap bitmap = + mXmppConnectionService + .getFileBackend() + .cropCenter(uri, bottom - top, right - left); + if (bitmap != null) { + drawTile(canvas, bitmap, left, top, right, bottom); + return true; + } + } + return false; + } + + private boolean drawTile( + Canvas canvas, Bitmap bm, int dstleft, int dsttop, int dstright, int dstbottom) { + Rect dst = new Rect(dstleft, dsttop, dstright, dstbottom); + canvas.drawBitmap(bm, null, dst, null); + return true; + } + + @Override + public void onAdvancedStreamFeaturesAvailable(Account account) { + XmppConnection.Features features = account.getXmppConnection().getFeatures(); + if (features.pep() && !features.pepPersistent()) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": has pep but is not persistent"); + if (account.getAvatar() != null) { + mXmppConnectionService.republishAvatarIfNeeded(account); + } + } + } + + private static String emptyOnNull(@Nullable Jid value) { + return value == null ? "" : value.toString(); + } + + public interface Avatarable { + @ColorInt + int getAvatarBackgroundColor(); + + String getAvatarName(); + } } diff --git a/src/main/java/eu/siacs/conversations/services/CallIntegration.java b/src/main/java/eu/siacs/conversations/services/CallIntegration.java index f4d4fae35..747f79fb3 100644 --- a/src/main/java/eu/siacs/conversations/services/CallIntegration.java +++ b/src/main/java/eu/siacs/conversations/services/CallIntegration.java @@ -455,7 +455,7 @@ public class CallIntegration extends Connection { } public static Uri address(final Jid contact) { - return Uri.parse(String.format("xmpp:%s", contact.toEscapedString())); + return Uri.parse(String.format("xmpp:%s", contact.toString())); } public void verifyDisconnected() { diff --git a/src/main/java/eu/siacs/conversations/services/CallIntegrationConnectionService.java b/src/main/java/eu/siacs/conversations/services/CallIntegrationConnectionService.java index a225dcbee..359504396 100644 --- a/src/main/java/eu/siacs/conversations/services/CallIntegrationConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/CallIntegrationConnectionService.java @@ -20,14 +20,11 @@ import android.telecom.TelecomManager; import android.telecom.VideoProfile; import android.util.Log; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; - import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; @@ -40,7 +37,6 @@ import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection; import eu.siacs.conversations.xmpp.jingle.Media; import eu.siacs.conversations.xmpp.jingle.RtpEndUserState; import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; - import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -91,7 +87,8 @@ public class CallIntegrationConnectionService extends ConnectionService { if (service == null) { Log.d( Config.LOGTAG, - "CallIntegrationConnection service was unable to bind to XmppConnectionService"); + "CallIntegrationConnection service was unable to bind to" + + " XmppConnectionService"); return Connection.createFailedConnection( new DisconnectCause(DisconnectCause.ERROR, "service connection not found")); } @@ -107,8 +104,8 @@ public class CallIntegrationConnectionService extends ConnectionService { Log.d(Config.LOGTAG, "create outgoing rtp connection!"); final Intent intent = new Intent(service, RtpSessionActivity.class); intent.setAction(Intent.ACTION_VIEW); - intent.putExtra(RtpSessionActivity.EXTRA_ACCOUNT, account.getJid().toEscapedString()); - intent.putExtra(RtpSessionActivity.EXTRA_WITH, with.toEscapedString()); + intent.putExtra(RtpSessionActivity.EXTRA_ACCOUNT, account.getJid().toString()); + intent.putExtra(RtpSessionActivity.EXTRA_WITH, with.toString()); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); final Connection callIntegration; @@ -135,7 +132,8 @@ public class CallIntegrationConnectionService extends ConnectionService { return Connection.createFailedConnection( new DisconnectCause( DisconnectCause.ERROR, - "Phone is busy. Probably race condition. Try again in a moment")); + "Phone is busy. Probably race condition. Try again in a" + + " moment")); } if (proposal == null) { // TODO instead of just null checking try to get the sessionID @@ -187,9 +185,9 @@ public class CallIntegrationConnectionService extends ConnectionService { } final Jid jid; if ("tel".equals(uri.getScheme())) { - jid = Jid.ofEscaped(extras.getString(EXTRA_ADDRESS)); + jid = Jid.of(extras.getString(EXTRA_ADDRESS)); } else { - jid = Jid.ofEscaped(uri.getSchemeSpecificPart()); + jid = Jid.of(uri.getSchemeSpecificPart()); } final int videoState = extras.getInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE); final Set<Media> media = @@ -226,7 +224,7 @@ public class CallIntegrationConnectionService extends ConnectionService { return Connection.createFailedConnection( new DisconnectCause(DisconnectCause.ERROR, "service connection not found")); } - final var jid = Jid.ofEscaped(uri.getSchemeSpecificPart()); + final var jid = Jid.of(uri.getSchemeSpecificPart()); final Account account = service.findAccountByUuid(phoneAccountHandle.getId()); final var weakReference = service.getJingleConnectionManager().findJingleRtpConnection(account, jid, sid); @@ -365,7 +363,7 @@ public class CallIntegrationConnectionService extends ConnectionService { } else { // for Android 8 we need to put in a fake tel uri final var outgoingCallExtras = new Bundle(); - outgoingCallExtras.putString(EXTRA_ADDRESS, with.toEscapedString()); + outgoingCallExtras.putString(EXTRA_ADDRESS, with.toString()); extras.putBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, outgoingCallExtras); address = Uri.parse("tel:0"); } diff --git a/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java b/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java index 5a8c58ac6..9e09e56e9 100644 --- a/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java +++ b/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java @@ -1,14 +1,10 @@ package eu.siacs.conversations.services; - import android.util.Log; - import androidx.annotation.NonNull; - import com.google.common.base.Strings; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; - import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Room; @@ -17,18 +13,7 @@ import eu.siacs.conversations.http.services.MuclumbusService; import eu.siacs.conversations.parser.IqParser; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.XmppConnection; - import im.conversations.android.xmpp.model.stanza.Iq; - -import okhttp3.OkHttpClient; -import okhttp3.ResponseBody; - -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; - import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -38,6 +23,13 @@ import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import okhttp3.OkHttpClient; +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; public class ChannelDiscoveryService { @@ -57,7 +49,8 @@ public class ChannelDiscoveryService { this.muclumbusService = null; return; } - final OkHttpClient.Builder builder = HttpConnectionManager.okHttpClient(service).newBuilder(); + final OkHttpClient.Builder builder = + HttpConnectionManager.okHttpClient(service).newBuilder(); if (service.useTorToConnect()) { builder.proxy(HttpConnectionManager.getProxy()); } @@ -205,10 +198,8 @@ public class ChannelDiscoveryService { account, infoRequest, infoResponse -> { - if (infoResponse.getType() - == Iq.Type.RESULT) { - final Room room = - IqParser.parseRoom(infoResponse); + if (infoResponse.getType() == Iq.Type.RESULT) { + final Room room = IqParser.parseRoom(infoResponse); if (room != null) { rooms.add(room); } @@ -260,7 +251,7 @@ public class ChannelDiscoveryService { continue; } for (final String mucService : xmppConnection.getMucServers()) { - Jid jid = Jid.ofEscaped(mucService); + final Jid jid = Jid.of(mucService); if (!localMucServices.containsKey(jid)) { localMucServices.put(jid, account); } diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index 83bef27e7..cd2045774 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -573,9 +573,8 @@ public class NotificationService { final Intent fullScreenIntent = new Intent(mXmppConnectionService, RtpSessionActivity.class); fullScreenIntent.putExtra( - RtpSessionActivity.EXTRA_ACCOUNT, - id.account.getJid().asBareJid().toEscapedString()); - fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_WITH, id.with.toEscapedString()); + RtpSessionActivity.EXTRA_ACCOUNT, id.account.getJid().asBareJid().toString()); + fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_WITH, id.with.toString()); fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, id.sessionId); fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); @@ -702,9 +701,8 @@ public class NotificationService { new Intent(mXmppConnectionService, RtpSessionActivity.class); fullScreenIntent.setAction(action); fullScreenIntent.putExtra( - RtpSessionActivity.EXTRA_ACCOUNT, - id.account.getJid().asBareJid().toEscapedString()); - fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_WITH, id.with.toEscapedString()); + RtpSessionActivity.EXTRA_ACCOUNT, id.account.getJid().asBareJid().toString()); + fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_WITH, id.with.toString()); fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, id.sessionId); return PendingIntent.getActivity( mXmppConnectionService, @@ -1902,7 +1900,7 @@ public class NotificationService { } else if (errors.size() == 1) { mBuilder.setContentTitle( mXmppConnectionService.getString(R.string.problem_connecting_to_account)); - mBuilder.setContentText(errors.get(0).getJid().asBareJid().toEscapedString()); + mBuilder.setContentText(errors.get(0).getJid().asBareJid().toString()); } else { mBuilder.setContentTitle( mXmppConnectionService.getString(R.string.problem_connecting_to_accounts)); @@ -1961,7 +1959,7 @@ public class NotificationService { intent = new Intent(mXmppConnectionService, AccountUtils.MANAGE_ACCOUNT_ACTIVITY); } else { intent = new Intent(mXmppConnectionService, EditAccountActivity.class); - intent.putExtra("jid", errors.get(0).getJid().asBareJid().toEscapedString()); + intent.putExtra("jid", errors.get(0).getJid().asBareJid().toString()); intent.putExtra(EditAccountActivity.EXTRA_OPENED_FROM_NOTIFICATION, true); } mBuilder.setContentIntent( diff --git a/src/main/java/eu/siacs/conversations/services/ShortcutService.java b/src/main/java/eu/siacs/conversations/services/ShortcutService.java index b2a3cce75..132f70912 100644 --- a/src/main/java/eu/siacs/conversations/services/ShortcutService.java +++ b/src/main/java/eu/siacs/conversations/services/ShortcutService.java @@ -159,17 +159,15 @@ public class ShortcutService { } private static String getShortcutId(final Contact contact) { - return contact.getAccount().getJid().asBareJid().toEscapedString() + return contact.getAccount().getJid().asBareJid().toString() + "#" - + contact.getJid().asBareJid().toEscapedString(); + + contact.getJid().asBareJid().toString(); } private static String getShortcutId(final MucOptions mucOptions) { final Account account = mucOptions.getAccount(); final Jid jid = mucOptions.getConversation().getJid(); - return account.getJid().asBareJid().toEscapedString() - + "#" - + jid.asBareJid().toEscapedString(); + return account.getJid().asBareJid().toString() + "#" + jid.asBareJid().toString(); } private Intent getShortcutIntent(final MucOptions mucOptions) { @@ -179,17 +177,12 @@ public class ShortcutService { Uri.parse( String.format( "xmpp:%s?join", - mucOptions - .getConversation() - .getJid() - .asBareJid() - .toEscapedString()))); + mucOptions.getConversation().getJid().asBareJid().toString()))); } private Intent getShortcutIntent(final Contact contact) { return getShortcutIntent( - contact.getAccount(), - Uri.parse("xmpp:" + contact.getJid().asBareJid().toEscapedString())); + contact.getAccount(), Uri.parse("xmpp:" + contact.getJid().asBareJid().toString())); } private Intent getShortcutIntent(final Account account, final Uri uri) { diff --git a/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java b/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java index 92ae9d9ec..24aaf1f42 100644 --- a/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java +++ b/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java @@ -10,10 +10,8 @@ import android.os.Messenger; import android.os.RemoteException; import android.preference.PreferenceManager; import android.util.Log; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.common.base.Optional; import com.google.common.base.Strings; import com.google.common.collect.Iterables; @@ -22,7 +20,6 @@ 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.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; @@ -34,7 +31,6 @@ import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.Jid; import im.conversations.android.xmpp.model.stanza.Iq; import im.conversations.android.xmpp.model.stanza.Presence; - import java.nio.charset.StandardCharsets; import java.text.ParseException; import java.util.List; @@ -92,7 +88,8 @@ public class UnifiedPushBroker { renewUnifiedPushEndpoints(null); } - public Optional<Transport> renewUnifiedPushEndpoints(@Nullable final PushTargetMessenger pushTargetMessenger) { + public Optional<Transport> renewUnifiedPushEndpoints( + @Nullable final PushTargetMessenger pushTargetMessenger) { final Optional<Transport> transportOptional = getTransport(); if (transportOptional.isPresent()) { final Transport transport = transportOptional.get(); @@ -100,13 +97,13 @@ public class UnifiedPushBroker { renewUnifiedEndpoint(transportOptional.get(), pushTargetMessenger); } else { if (pushTargetMessenger != null && pushTargetMessenger.messenger != null) { - sendRegistrationDelayed(pushTargetMessenger.messenger,"account is disabled"); + sendRegistrationDelayed(pushTargetMessenger.messenger, "account is disabled"); } Log.d(Config.LOGTAG, "skipping UnifiedPush endpoint renewal. Account is disabled"); } } else { if (pushTargetMessenger != null && pushTargetMessenger.messenger != null) { - sendRegistrationDelayed(pushTargetMessenger.messenger,"no transport selected"); + sendRegistrationDelayed(pushTargetMessenger.messenger, "no transport selected"); } Log.d(Config.LOGTAG, "skipping UnifiedPush endpoint renewal. No transport selected"); } @@ -121,16 +118,16 @@ public class UnifiedPushBroker { try { messenger.send(message); } catch (final RemoteException e) { - Log.d(Config.LOGTAG,"unable to tell messenger of delayed registration",e); + Log.d(Config.LOGTAG, "unable to tell messenger of delayed registration", e); } } - private void renewUnifiedEndpoint(final Transport transport, final PushTargetMessenger pushTargetMessenger) { + private void renewUnifiedEndpoint( + final Transport transport, final PushTargetMessenger pushTargetMessenger) { final Account account = transport.account; final UnifiedPushDatabase unifiedPushDatabase = UnifiedPushDatabase.getInstance(service); final List<UnifiedPushDatabase.PushTarget> renewals = - unifiedPushDatabase.getRenewals( - account.getUuid(), transport.transport.toEscapedString()); + unifiedPushDatabase.getRenewals(account.getUuid(), transport.transport.toString()); Log.d( Config.LOGTAG, account.getJid().asBareJid() @@ -142,7 +139,11 @@ public class UnifiedPushBroker { Log.d( Config.LOGTAG, account.getJid().asBareJid() + ": try to renew UnifiedPush " + renewal); - UnifiedPushDistributor.quickLog(service,String.format("%s: try to renew UnifiedPush %s", account.getJid(), renewal.toString())); + UnifiedPushDistributor.quickLog( + service, + String.format( + "%s: try to renew UnifiedPush %s", + account.getJid(), renewal.toString())); final String hashedApplication = UnifiedPushDistributor.hash(account.getUuid(), renewal.application); final String hashedInstance = @@ -205,7 +206,7 @@ public class UnifiedPushBroker { unifiedPushDatabase.updateEndpoint( renewal.instance, transport.account.getUuid(), - transport.transport.toEscapedString(), + transport.transport.toString(), endpoint, expiration); if (modified) { @@ -231,15 +232,21 @@ public class UnifiedPushBroker { } } - private void sendEndpoint(final Messenger messenger, String instance, final UnifiedPushDatabase.ApplicationEndpoint applicationEndpoint) { + private void sendEndpoint( + final Messenger messenger, + String instance, + final UnifiedPushDatabase.ApplicationEndpoint applicationEndpoint) { if (messenger != null) { - Log.d(Config.LOGTAG,"using messenger instead of broadcast to communicate endpoint to "+applicationEndpoint.application); + Log.d( + Config.LOGTAG, + "using messenger instead of broadcast to communicate endpoint to " + + applicationEndpoint.application); final Message message = new Message(); message.obj = endpointIntent(instance, applicationEndpoint); try { messenger.send(message); } catch (final RemoteException e) { - Log.d(Config.LOGTAG,"messenger failed. falling back to broadcast"); + Log.d(Config.LOGTAG, "messenger failed. falling back to broadcast"); broadcastEndpoint(instance, applicationEndpoint); } } else { @@ -281,8 +288,7 @@ public class UnifiedPushBroker { future, new FutureCallback<>() { @Override - public void onSuccess( - final List<UnifiedPushDatabase.PushTarget> pushTargets) { + public void onSuccess(final List<UnifiedPushDatabase.PushTarget> pushTargets) { broadcastUnregistered(pushTargets); } @@ -290,19 +296,21 @@ public class UnifiedPushBroker { public void onFailure(@NonNull Throwable throwable) { Log.d( Config.LOGTAG, - "could not delete endpoints after UnifiedPushDistributor was disabled"); + "could not delete endpoints after UnifiedPushDistributor was" + + " disabled"); } }, MoreExecutors.directExecutor()); } private ListenableFuture<List<UnifiedPushDatabase.PushTarget>> deletePushTargets() { - return Futures.submit(() -> UnifiedPushDatabase.getInstance(service).deletePushTargets(),SCHEDULER); + return Futures.submit( + () -> UnifiedPushDatabase.getInstance(service).deletePushTargets(), SCHEDULER); } private void broadcastUnregistered(final List<UnifiedPushDatabase.PushTarget> pushTargets) { - for(final UnifiedPushDatabase.PushTarget pushTarget : pushTargets) { - Log.d(Config.LOGTAG,"sending unregistered to "+pushTarget); + for (final UnifiedPushDatabase.PushTarget pushTarget : pushTargets) { + Log.d(Config.LOGTAG, "sending unregistered to " + pushTarget); broadcastUnregistered(pushTarget); } } @@ -368,8 +376,8 @@ public class UnifiedPushBroker { final Jid transport; final Jid jid; try { - transport = Jid.ofEscaped(Strings.nullToEmpty(pushServerPreference).trim()); - jid = Jid.ofEscaped(Strings.nullToEmpty(accountPreference).trim()); + transport = Jid.of(Strings.nullToEmpty(pushServerPreference).trim()); + jid = Jid.of(Strings.nullToEmpty(accountPreference).trim()); } catch (final IllegalArgumentException e) { return Optional.absent(); } @@ -390,8 +398,7 @@ public class UnifiedPushBroker { } final String uuid = account.getUuid(); final List<UnifiedPushDatabase.PushTarget> pushTargets = - UnifiedPushDatabase.getInstance(service) - .getPushTargets(uuid, transport.toEscapedString()); + UnifiedPushDatabase.getInstance(service).getPushTargets(uuid, transport.toString()); return Iterables.tryFind( pushTargets, pt -> @@ -422,7 +429,8 @@ public class UnifiedPushBroker { service.sendBroadcast(updateIntent); } - private Intent endpointIntent(final String instance, final UnifiedPushDatabase.ApplicationEndpoint endpoint) { + private Intent endpointIntent( + final String instance, final UnifiedPushDatabase.ApplicationEndpoint endpoint) { final Intent intent = new Intent(UnifiedPushDistributor.ACTION_NEW_ENDPOINT); intent.setPackage(endpoint.application); intent.putExtra("token", instance); @@ -449,13 +457,12 @@ public class UnifiedPushBroker { return intent; } - public void rebroadcastEndpoint(final Messenger messenger, final String instance, final Transport transport) { + public void rebroadcastEndpoint( + final Messenger messenger, final String instance, final Transport transport) { final UnifiedPushDatabase unifiedPushDatabase = UnifiedPushDatabase.getInstance(service); final UnifiedPushDatabase.ApplicationEndpoint endpoint = unifiedPushDatabase.getEndpoint( - transport.account.getUuid(), - transport.transport.toEscapedString(), - instance); + transport.account.getUuid(), transport.transport.toString(), instance); if (endpoint != null) { sendEndpoint(messenger, instance, endpoint); } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index c9c116c3a..9a5ea426f 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -120,7 +120,6 @@ import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.LocalizedContent; import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnContactStatusChanged; import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; @@ -2069,7 +2068,7 @@ public class XmppConnectionService extends Service { if (uri != null) { final EasyOnboardingInvite invite = new EasyOnboardingInvite( - jid.getDomain().toEscapedString(), uri, landingUrl); + jid.getDomain().toString(), uri, landingUrl); callback.inviteRequested(invite); return; } @@ -2145,7 +2144,7 @@ public class XmppConnectionService extends Service { public void processMdsItem(final Account account, final Element item) { final Jid jid = - item == null ? null : InvalidJid.getNullForInvalid(item.getAttributeAsJid("id")); + item == null ? null : Jid.Invalid.getNullForInvalid(item.getAttributeAsJid("id")); if (jid == null) { return; } @@ -2306,7 +2305,7 @@ public class XmppConnectionService extends Service { account, Namespace.BOOKMARKS2, item, - bookmark.getJid().asBareJid().toEscapedString(), + bookmark.getJid().asBareJid().toString(), PublishOptions.persistentWhitelistAccessMaxItems()); } else if (connection.getFeatures().bookmarksConversion()) { pushBookmarksPep(account); @@ -2321,7 +2320,7 @@ public class XmppConnectionService extends Service { if (connection.getFeatures().bookmarks2()) { final Iq request = mIqGenerator.deleteItem( - Namespace.BOOKMARKS2, bookmark.getJid().asBareJid().toEscapedString()); + Namespace.BOOKMARKS2, bookmark.getJid().asBareJid().toString()); Log.d( Config.LOGTAG, account.getJid().asBareJid() + ": removing bookmark via Bookmarks 2"); @@ -3027,10 +3026,10 @@ public class XmppConnectionService extends Service { } private void provisionAccount(final String address, final String password) { - final Jid jid = Jid.ofEscaped(address); + final Jid jid = Jid.of(address); final Account account = new Account(jid, password); account.setOption(Account.OPTION_DISABLED, true); - Log.d(Config.LOGTAG, jid.asBareJid().toEscapedString() + ": provisioning account"); + Log.d(Config.LOGTAG, jid.asBareJid().toString() + ": provisioning account"); createAccount(account); } @@ -5704,7 +5703,7 @@ public class XmppConnectionService extends Service { account, Namespace.MDS_DISPLAYED, item, - itemId.toEscapedString(), + itemId.toString(), PublishOptions.persistentWhitelistAccessMaxItems()); } @@ -5819,7 +5818,7 @@ public class XmppConnectionService extends Service { if (Config.QUICKSY_DOMAIN != null) { hosts.remove( Config.QUICKSY_DOMAIN - .toEscapedString()); // we only want to show this when we type a e164 + .toString()); // we only want to show this when we type a e164 // number } if (Config.MAGIC_CREATE_DOMAIN != null) { @@ -5835,7 +5834,7 @@ public class XmppConnectionService extends Service { mucServers.addAll(account.getXmppConnection().getMucServers()); for (final Bookmark bookmark : account.getBookmarks()) { final Jid jid = bookmark.getJid(); - final String s = jid == null ? null : jid.getDomain().toEscapedString(); + final String s = jid == null ? null : jid.getDomain().toString(); if (s != null) { mucServers.add(s); } diff --git a/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java b/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java index 04678c3c7..195ca74f2 100644 --- a/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java +++ b/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java @@ -2,13 +2,9 @@ package eu.siacs.conversations.ui; import android.view.View; import android.widget.Toast; - import androidx.annotation.StringRes; -import androidx.appcompat.app.AlertDialog; import androidx.databinding.DataBindingUtil; - import com.google.android.material.dialog.MaterialAlertDialogBuilder; - import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.DialogBlockContactBinding; import eu.siacs.conversations.entities.Blockable; @@ -17,43 +13,56 @@ import eu.siacs.conversations.ui.util.JidDialog; public final class BlockContactDialog { - public static void show(final XmppActivity xmppActivity, final Blockable blockable) { - show(xmppActivity, blockable, null); - } - public static void show(final XmppActivity xmppActivity, final Blockable blockable, final String serverMsgId) { - final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(xmppActivity); - final boolean isBlocked = blockable.isBlocked(); - builder.setNegativeButton(R.string.cancel, null); - DialogBlockContactBinding binding = DataBindingUtil.inflate(xmppActivity.getLayoutInflater(), R.layout.dialog_block_contact, null, false); - final boolean reporting = blockable.getAccount().getXmppConnection().getFeatures().spamReporting(); - if (reporting && !isBlocked) { - binding.reportSpam.setVisibility(View.VISIBLE); - if (serverMsgId != null) { - binding.reportSpam.setChecked(true); - binding.reportSpam.setEnabled(false); - } else { - binding.reportSpam.setEnabled(true); - } - } else { - binding.reportSpam.setVisibility(View.GONE); - } - builder.setView(binding.getRoot()); + public static void show(final XmppActivity xmppActivity, final Blockable blockable) { + show(xmppActivity, blockable, null); + } + + public static void show( + final XmppActivity xmppActivity, final Blockable blockable, final String serverMsgId) { + final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(xmppActivity); + final boolean isBlocked = blockable.isBlocked(); + builder.setNegativeButton(R.string.cancel, null); + DialogBlockContactBinding binding = + DataBindingUtil.inflate( + xmppActivity.getLayoutInflater(), + R.layout.dialog_block_contact, + null, + false); + final boolean reporting = + blockable.getAccount().getXmppConnection().getFeatures().spamReporting(); + if (reporting && !isBlocked) { + binding.reportSpam.setVisibility(View.VISIBLE); + if (serverMsgId != null) { + binding.reportSpam.setChecked(true); + binding.reportSpam.setEnabled(false); + } else { + binding.reportSpam.setEnabled(true); + } + } else { + binding.reportSpam.setVisibility(View.GONE); + } + builder.setView(binding.getRoot()); - final String value; - @StringRes int res; - if (blockable.getJid().isFullJid()) { - builder.setTitle(isBlocked ? R.string.action_unblock_participant : R.string.action_block_participant); - value = blockable.getJid().toEscapedString(); - res = isBlocked ? R.string.unblock_contact_text : R.string.block_contact_text; - } else if (blockable.getJid().getLocal() == null || blockable.getAccount().isBlocked(blockable.getJid().getDomain())) { - builder.setTitle(isBlocked ? R.string.action_unblock_domain : R.string.action_block_domain); - value =blockable.getJid().getDomain().toEscapedString(); - res = isBlocked ? R.string.unblock_domain_text : R.string.block_domain_text; - } else { - if (isBlocked) { - builder.setTitle(R.string.action_unblock_contact); - } else if (serverMsgId != null) { - builder.setTitle(R.string.report_spam_and_block); + final String value; + @StringRes int res; + if (blockable.getJid().isFullJid()) { + builder.setTitle( + isBlocked + ? R.string.action_unblock_participant + : R.string.action_block_participant); + value = blockable.getJid().toString(); + res = isBlocked ? R.string.unblock_contact_text : R.string.block_contact_text; + } else if (blockable.getJid().getLocal() == null + || blockable.getAccount().isBlocked(blockable.getJid().getDomain())) { + builder.setTitle( + isBlocked ? R.string.action_unblock_domain : R.string.action_block_domain); + value = blockable.getJid().getDomain().toString(); + res = isBlocked ? R.string.unblock_domain_text : R.string.block_domain_text; + } else { + if (isBlocked) { + builder.setTitle(R.string.action_unblock_contact); + } else if (serverMsgId != null) { + builder.setTitle(R.string.report_spam_and_block); } else { final int resBlockAction = blockable instanceof Conversation @@ -61,28 +70,39 @@ public final class BlockContactDialog { ? R.string.block_stranger : R.string.action_block_contact; builder.setTitle(resBlockAction); - } - value = blockable.getJid().asBareJid().toEscapedString(); - res = isBlocked ? R.string.unblock_contact_text : R.string.block_contact_text; - } - binding.text.setText(JidDialog.style(xmppActivity, res, value)); - builder.setPositiveButton(isBlocked ? R.string.unblock : R.string.block, (dialog, which) -> { - if (isBlocked) { - xmppActivity.xmppConnectionService.sendUnblockRequest(blockable); - } else { - boolean toastShown = false; - if (xmppActivity.xmppConnectionService.sendBlockRequest(blockable, binding.reportSpam.isChecked(), serverMsgId)) { - Toast.makeText(xmppActivity, R.string.corresponding_chats_closed, Toast.LENGTH_SHORT).show(); - toastShown = true; - } - if (xmppActivity instanceof ContactDetailsActivity) { - if (!toastShown) { - Toast.makeText(xmppActivity, R.string.contact_blocked_past_tense, Toast.LENGTH_SHORT).show(); - } - xmppActivity.finish(); - } - } - }); - builder.create().show(); - } + } + value = blockable.getJid().asBareJid().toString(); + res = isBlocked ? R.string.unblock_contact_text : R.string.block_contact_text; + } + binding.text.setText(JidDialog.style(xmppActivity, res, value)); + builder.setPositiveButton( + isBlocked ? R.string.unblock : R.string.block, + (dialog, which) -> { + if (isBlocked) { + xmppActivity.xmppConnectionService.sendUnblockRequest(blockable); + } else { + boolean toastShown = false; + if (xmppActivity.xmppConnectionService.sendBlockRequest( + blockable, binding.reportSpam.isChecked(), serverMsgId)) { + Toast.makeText( + xmppActivity, + R.string.corresponding_chats_closed, + Toast.LENGTH_SHORT) + .show(); + toastShown = true; + } + if (xmppActivity instanceof ContactDetailsActivity) { + if (!toastShown) { + Toast.makeText( + xmppActivity, + R.string.contact_blocked_past_tense, + Toast.LENGTH_SHORT) + .show(); + } + xmppActivity.finish(); + } + } + }); + builder.create().show(); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java b/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java index 0a59f5348..71e1f25aa 100644 --- a/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java @@ -3,12 +3,8 @@ package eu.siacs.conversations.ui; import android.os.Bundle; import android.text.Editable; import android.widget.Toast; - import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; - -import java.util.Collections; - import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Blockable; @@ -17,97 +13,107 @@ import eu.siacs.conversations.entities.RawBlockable; import eu.siacs.conversations.ui.interfaces.OnBackendConnected; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; +import java.util.Collections; -public class BlocklistActivity extends AbstractSearchableListItemActivity implements OnUpdateBlocklist { - - private Account account = null; +public class BlocklistActivity extends AbstractSearchableListItemActivity + implements OnUpdateBlocklist { - @Override - public void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getListView().setOnItemLongClickListener((parent, view, position, id) -> { - BlockContactDialog.show(BlocklistActivity.this, (Blockable) getListItems().get(position)); - return true; - }); - this.binding.fab.show(); - this.binding.fab.setOnClickListener((v)->showEnterJidDialog()); - } + private Account account = null; - @Override - public void onBackendConnected() { - for (final Account account : xmppConnectionService.getAccounts()) { - if (account.getJid().toEscapedString().equals(getIntent().getStringExtra(EXTRA_ACCOUNT))) { - this.account = account; - break; - } - } - filterContacts(); - Fragment fragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG); - if (fragment instanceof OnBackendConnected) { - ((OnBackendConnected) fragment).onBackendConnected(); - } - } + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getListView() + .setOnItemLongClickListener( + (parent, view, position, id) -> { + BlockContactDialog.show( + BlocklistActivity.this, + (Blockable) getListItems().get(position)); + return true; + }); + this.binding.fab.show(); + this.binding.fab.setOnClickListener((v) -> showEnterJidDialog()); + } - @Override - protected void filterContacts(final String needle) { - getListItems().clear(); - if (account != null) { - for (final Jid jid : account.getBlocklist()) { - ListItem item; - if (jid.isFullJid()) { - item = new RawBlockable(account, jid); - } else { - item = account.getRoster().getContact(jid); - } - if (item.match(this, needle)) { - getListItems().add(item); - } - } - Collections.sort(getListItems()); - } - getListItemAdapter().notifyDataSetChanged(); - } + @Override + public void onBackendConnected() { + for (final Account account : xmppConnectionService.getAccounts()) { + if (account.getJid().toString().equals(getIntent().getStringExtra(EXTRA_ACCOUNT))) { + this.account = account; + break; + } + } + filterContacts(); + Fragment fragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG); + if (fragment instanceof OnBackendConnected) { + ((OnBackendConnected) fragment).onBackendConnected(); + } + } - protected void showEnterJidDialog() { - FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - Fragment prev = getSupportFragmentManager().findFragmentByTag("dialog"); - if (prev != null) { - ft.remove(prev); - } - ft.addToBackStack(null); - EnterJidDialog dialog = EnterJidDialog.newInstance( - null, - getString(R.string.block_jabber_id), - getString(R.string.block), - null, - account.getJid().asBareJid().toEscapedString(), - true, - false - ); + @Override + protected void filterContacts(final String needle) { + getListItems().clear(); + if (account != null) { + for (final Jid jid : account.getBlocklist()) { + ListItem item; + if (jid.isFullJid()) { + item = new RawBlockable(account, jid); + } else { + item = account.getRoster().getContact(jid); + } + if (item.match(this, needle)) { + getListItems().add(item); + } + } + Collections.sort(getListItems()); + } + getListItemAdapter().notifyDataSetChanged(); + } - dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid) -> { - Blockable blockable = new RawBlockable(account, contactJid); - if (xmppConnectionService.sendBlockRequest(blockable, false, null)) { - Toast.makeText(BlocklistActivity.this, R.string.corresponding_chats_closed, Toast.LENGTH_SHORT).show(); - } - return true; - }); + protected void showEnterJidDialog() { + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + Fragment prev = getSupportFragmentManager().findFragmentByTag("dialog"); + if (prev != null) { + ft.remove(prev); + } + ft.addToBackStack(null); + EnterJidDialog dialog = + EnterJidDialog.newInstance( + null, + getString(R.string.block_jabber_id), + getString(R.string.block), + null, + account.getJid().asBareJid().toString(), + true, + false); - dialog.show(ft, "dialog"); - } + dialog.setOnEnterJidDialogPositiveListener( + (accountJid, contactJid) -> { + Blockable blockable = new RawBlockable(account, contactJid); + if (xmppConnectionService.sendBlockRequest(blockable, false, null)) { + Toast.makeText( + BlocklistActivity.this, + R.string.corresponding_chats_closed, + Toast.LENGTH_SHORT) + .show(); + } + return true; + }); - protected void refreshUiReal() { - final Editable editable = getSearchEditText().getText(); - if (editable != null) { - filterContacts(editable.toString()); - } else { - filterContacts(); - } - } + dialog.show(ft, "dialog"); + } - @Override - public void OnUpdateBlocklist(final OnUpdateBlocklist.Status status) { - refreshUi(); - } + protected void refreshUiReal() { + final Editable editable = getSearchEditText().getText(); + if (editable != null) { + filterContacts(editable.toString()); + } else { + filterContacts(); + } + } + @Override + public void OnUpdateBlocklist(final OnUpdateBlocklist.Status status) { + refreshUi(); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java b/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java index 420494d4d..a13324730 100644 --- a/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java @@ -294,7 +294,7 @@ public class ChannelDiscoveryActivity extends XmppActivity } public void joinChannelSearchResult(final String selectedAccount, final Room result) { - final Jid jid = Jid.ofEscaped(selectedAccount); + final Jid jid = Jid.of(selectedAccount); final Account account = xmppConnectionService.findAccountByJid(jid); final Conversation conversation = xmppConnectionService.findOrCreateConversation( diff --git a/src/main/java/eu/siacs/conversations/ui/ChooseAccountForProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/ChooseAccountForProfilePictureActivity.java index 71662589c..ff60e6419 100644 --- a/src/main/java/eu/siacs/conversations/ui/ChooseAccountForProfilePictureActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ChooseAccountForProfilePictureActivity.java @@ -4,14 +4,11 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.widget.Toast; - import androidx.databinding.DataBindingUtil; - import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityManageAccountsBinding; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.ui.adapter.AccountAdapter; - import java.util.ArrayList; import java.util.List; @@ -29,16 +26,18 @@ public class ChooseAccountForProfilePictureActivity extends XmppActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - final ActivityManageAccountsBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_manage_accounts); + final ActivityManageAccountsBinding binding = + DataBindingUtil.setContentView(this, R.layout.activity_manage_accounts); Activities.setStatusAndNavigationBarColors(this, binding.getRoot()); setSupportActionBar(binding.toolbar); configureActionBar(getSupportActionBar(), false); this.mAccountAdapter = new AccountAdapter(this, accountList, false); binding.accountList.setAdapter(this.mAccountAdapter); - binding.accountList.setOnItemClickListener((arg0, view, position, arg3) -> { - final Account account = accountList.get(position); - goToProfilePictureActivity(account); - }); + binding.accountList.setOnItemClickListener( + (arg0, view, position, arg3) -> { + final Account account = accountList.get(position); + goToProfilePictureActivity(account); + }); } @Override @@ -58,7 +57,7 @@ public class ChooseAccountForProfilePictureActivity extends XmppActivity { private void loadEnabledAccounts() { accountList.clear(); - for(Account account : xmppConnectionService.getAccounts()) { + for (Account account : xmppConnectionService.getAccounts()) { if (account.isEnabled()) { accountList.add(account); } @@ -70,13 +69,17 @@ public class ChooseAccountForProfilePictureActivity extends XmppActivity { final Uri uri = startIntent == null ? null : startIntent.getData(); if (uri != null) { Intent intent = new Intent(this, PublishProfilePictureActivity.class); - intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toString()); intent.setData(uri); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); try { startActivity(intent); } catch (SecurityException e) { - Toast.makeText(this, R.string.sharing_application_not_grant_permission, Toast.LENGTH_SHORT).show(); + Toast.makeText( + this, + R.string.sharing_application_not_grant_permission, + Toast.LENGTH_SHORT) + .show(); return; } } diff --git a/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java b/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java index b9ebb413d..b6bcafbbc 100644 --- a/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java @@ -16,23 +16,12 @@ import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AdapterView; import android.widget.ListView; import android.widget.TextView; - import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.appcompat.app.ActionBar; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; - import com.google.common.base.Strings; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; @@ -44,8 +33,15 @@ import eu.siacs.conversations.ui.util.ActivityResult; import eu.siacs.conversations.ui.util.PendingItem; import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.Jid; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; -public class ChooseContactActivity extends AbstractSearchableListItemActivity implements MultiChoiceModeListener, AdapterView.OnItemClickListener { +public class ChooseContactActivity extends AbstractSearchableListItemActivity + implements MultiChoiceModeListener, AdapterView.OnItemClickListener { public static final String EXTRA_TITLE_RES_ID = "extra_title_res_id"; public static final String EXTRA_GROUP_CHAT_NAME = "extra_group_chat_name"; public static final String EXTRA_SELECT_MULTIPLE = "extra_select_multiple"; @@ -75,11 +71,11 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im } else { contacts.add(conversation.getJid().asBareJid().toString()); } - intent.putExtra(EXTRA_FILTERED_CONTACTS, contacts.toArray(new String[contacts.size()])); + intent.putExtra(EXTRA_FILTERED_CONTACTS, contacts.toArray(new String[0])); intent.putExtra(EXTRA_CONVERSATION, conversation.getUuid()); intent.putExtra(EXTRA_SELECT_MULTIPLE, true); intent.putExtra(EXTRA_SHOW_ENTER_JID, true); - intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().asBareJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().asBareJid().toString()); return intent; } @@ -135,8 +131,11 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im } final SharedPreferences preferences = getPreferences(); - this.startSearching = intent.getBooleanExtra("direct_search", false) && preferences.getBoolean("start_searching", getResources().getBoolean(R.bool.start_searching)); - + this.startSearching = + intent.getBooleanExtra("direct_search", false) + && preferences.getBoolean( + "start_searching", + getResources().getBoolean(R.bool.start_searching)); } private void onFabClicked(View v) { @@ -159,9 +158,11 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im binding.fab.setImageResource(R.drawable.ic_navigate_next_24dp); binding.fab.show(); final View view = getSearchEditText(); - final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + final InputMethodManager imm = + (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); if (view != null && imm != null) { - imm.hideSoftInputFromWindow(getSearchEditText().getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY); + imm.hideSoftInputFromWindow( + getSearchEditText().getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY); } return true; } @@ -226,11 +227,14 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im } } - public @StringRes - int getTitleFromIntent() { + public @StringRes int getTitleFromIntent() { final Intent intent = getIntent(); boolean multiple = intent != null && intent.getBooleanExtra(EXTRA_SELECT_MULTIPLE, false); - @StringRes int fallback = multiple ? R.string.title_activity_choose_contacts : R.string.title_activity_choose_contact; + @StringRes + int fallback = + multiple + ? R.string.title_activity_choose_contacts + : R.string.title_activity_choose_contact; return intent != null ? intent.getIntExtra(EXTRA_TITLE_RES_ID, fallback) : fallback; } @@ -239,7 +243,8 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im super.onCreateOptionsMenu(menu); final Intent i = getIntent(); boolean showEnterJid = i != null && i.getBooleanExtra(EXTRA_SHOW_ENTER_JID, false); - menu.findItem(R.id.action_scan_qr_code).setVisible(isCameraFeatureAvailable() && showEnterJid); + menu.findItem(R.id.action_scan_qr_code) + .setVisible(isCameraFeatureAvailable() && showEnterJid); MenuItem mMenuSearchView = menu.findItem(R.id.action_search); if (startSearching) { mMenuSearchView.expandActionView(); @@ -276,8 +281,8 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im for (final Account account : xmppConnectionService.getAccounts()) { if (account.isEnabled()) { for (final Contact contact : account.getRoster().getContacts()) { - if (contact.showInContactList() && - !filterContacts.contains(contact.getJid().asBareJid().toString()) + if (contact.showInContactList() + && !filterContacts.contains(contact.getJid().asBareJid().toString()) && contact.match(this, needle)) { getListItems().add(contact); } @@ -293,7 +298,7 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im } public void refreshUiReal() { - //nothing to do. This Activity doesn't implement any listeners + // nothing to do. This Activity doesn't implement any listeners } @Override @@ -314,28 +319,29 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im } ft.addToBackStack(null); Jid jid = uri == null ? null : uri.getJid(); - EnterJidDialog dialog = EnterJidDialog.newInstance( - mActivatedAccounts, - getString(R.string.enter_contact), - getString(R.string.select), - jid == null ? null : jid.asBareJid().toString(), - getIntent().getStringExtra(EXTRA_ACCOUNT), - true, - false - ); - - dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid) -> { - final Intent request = getIntent(); - final Intent data = new Intent(); - data.putExtra("contact", contactJid.toString()); - data.putExtra(EXTRA_ACCOUNT, accountJid.toEscapedString()); - data.putExtra(EXTRA_SELECT_MULTIPLE, false); - copy(request, data); - setResult(RESULT_OK, data); - finish(); - - return true; - }); + EnterJidDialog dialog = + EnterJidDialog.newInstance( + mActivatedAccounts, + getString(R.string.enter_contact), + getString(R.string.select), + jid == null ? null : jid.asBareJid().toString(), + getIntent().getStringExtra(EXTRA_ACCOUNT), + true, + false); + + dialog.setOnEnterJidDialogPositiveListener( + (accountJid, contactJid) -> { + final Intent request = getIntent(); + final Intent data = new Intent(); + data.putExtra("contact", contactJid.toString()); + data.putExtra(EXTRA_ACCOUNT, accountJid.toString()); + data.putExtra(EXTRA_SELECT_MULTIPLE, false); + copy(request, data); + setResult(RESULT_OK, data); + finish(); + + return true; + }); dialog.show(ft, "dialog"); } @@ -352,7 +358,8 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im } private void handleActivityResult(ActivityResult activityResult) { - if (activityResult.resultCode == RESULT_OK && activityResult.requestCode == ScanActivity.REQUEST_SCAN_QR_CODE) { + if (activityResult.resultCode == RESULT_OK + && activityResult.requestCode == ScanActivity.REQUEST_SCAN_QR_CODE) { String result = activityResult.data.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT); XmppUri uri = new XmppUri(Strings.nullToEmpty(result)); if (uri.isValidJid()) { @@ -367,21 +374,23 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im this.mActivatedAccounts.clear(); for (final Account account : xmppConnectionService.getAccounts()) { if (account.isEnabled()) { - this.mActivatedAccounts.add(account.getJid().asBareJid().toEscapedString()); + this.mActivatedAccounts.add(account.getJid().asBareJid().toString()); } } ActivityResult activityResult = this.postponedActivityResult.pop(); if (activityResult != null) { handleActivityResult(activityResult); } - final Fragment fragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG); + final Fragment fragment = + getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG); if (fragment instanceof OnBackendConnected) { ((OnBackendConnected) fragment).onBackendConnected(); } } @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + public void onRequestPermissionsResult( + int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); ScanActivity.onRequestPermissionResult(this, requestCode, grantResults); } @@ -393,8 +402,10 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im getListView().setItemChecked(position, true); return; } - 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); final ListItem mListItem = getListItems().get(position); onListItemClicked(mListItem); } @@ -405,7 +416,7 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im data.putExtra("contact", item.getJid().toString()); String account = request.getStringExtra(EXTRA_ACCOUNT); if (account == null && item instanceof Contact) { - account = ((Contact) item).getAccount().getJid().asBareJid().toEscapedString(); + account = ((Contact) item).getAccount().getJid().asBareJid().toString(); } data.putExtra(EXTRA_ACCOUNT, account); data.putExtra(EXTRA_SELECT_MULTIPLE, false); diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index 8a1a0a133..bc526d8c2 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -405,8 +405,7 @@ public class ConferenceDetailsActivity extends XmppActivity if (mConversation != null) { if (http) { return "https://conversations.im/j/" - + XmppUri.lameUrlEncode( - mConversation.getJid().asBareJid().toEscapedString()); + + XmppUri.lameUrlEncode(mConversation.getJid().asBareJid().toString()); } else { return "xmpp:" + mConversation.getJid().asBareJid() + "?join"; } @@ -522,7 +521,7 @@ public class ConferenceDetailsActivity extends XmppActivity } final MucOptions mucOptions = mConversation.getMucOptions(); final User self = mucOptions.getSelf(); - final String account = mConversation.getAccount().getJid().asBareJid().toEscapedString(); + final String account = mConversation.getAccount().getJid().asBareJid().toString(); setTitle( mucOptions.isPrivateAndNonAnonymous() ? R.string.action_muc_details @@ -537,7 +536,7 @@ public class ConferenceDetailsActivity extends XmppActivity this.binding.jid.setText( getString(R.string.hosted_on, mConversation.getJid().getDomain())); } else { - this.binding.jid.setText(mConversation.getJid().asBareJid().toEscapedString()); + this.binding.jid.setText(mConversation.getJid().asBareJid().toString()); } AvatarWorkerTask.loadAvatar( mConversation, binding.yourPhoto, R.dimen.avatar_on_details_screen_size); @@ -682,7 +681,7 @@ public class ConferenceDetailsActivity extends XmppActivity @Override public void onAffiliationChangeFailed(Jid jid, int resId) { - displayToast(getString(resId, jid.asBareJid().toEscapedString())); + displayToast(getString(resId, jid.asBareJid().toString())); } @Override diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index 69e78124f..3738f8641 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -172,7 +172,7 @@ public class ContactDetailsActivity extends OmemoActivity if (quicksyContact) { value = PhoneNumberUtilWrapper.toFormattedPhoneNumber(this, jid); } else { - value = jid.toEscapedString(); + value = jid.toString(); } final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); builder.setTitle(getString(R.string.action_add_phone_book)); @@ -233,9 +233,9 @@ public class ContactDetailsActivity extends OmemoActivity protected String getShareableUri(boolean http) { if (http) { return "https://conversations.im/i/" - + XmppUri.lameUrlEncode(contact.getJid().asBareJid().toEscapedString()); + + XmppUri.lameUrlEncode(contact.getJid().asBareJid().toString()); } else { - return "xmpp:" + contact.getJid().asBareJid().toEscapedString(); + return "xmpp:" + contact.getJid().asBareJid().toString(); } } @@ -247,11 +247,11 @@ public class ContactDetailsActivity extends OmemoActivity && savedInstanceState.getBoolean("show_inactive_omemo", false); if (getIntent().getAction().equals(ACTION_VIEW_CONTACT)) { try { - this.accountJid = Jid.ofEscaped(getIntent().getExtras().getString(EXTRA_ACCOUNT)); + this.accountJid = Jid.of(getIntent().getExtras().getString(EXTRA_ACCOUNT)); } catch (final IllegalArgumentException ignored) { } try { - this.contactJid = Jid.ofEscaped(getIntent().getExtras().getString("contact")); + this.contactJid = Jid.of(getIntent().getExtras().getString("contact")); } catch (final IllegalArgumentException ignored) { } } @@ -328,7 +328,7 @@ public class ContactDetailsActivity extends OmemoActivity JidDialog.style( this, R.string.remove_contact_text, - contact.getJid().toEscapedString())) + contact.getJid().toString())) .setPositiveButton(getString(R.string.delete), removeFromRoster) .create() .show(); @@ -506,7 +506,7 @@ public class ContactDetailsActivity extends OmemoActivity } binding.detailsContactjid.setText(IrregularUnicodeDetector.style(this, contact.getJid())); - final String account = contact.getAccount().getJid().asBareJid().toEscapedString(); + final String account = contact.getAccount().getJid().asBareJid().toString(); binding.detailsAccount.setText(getString(R.string.using_account, account)); AvatarWorkerTask.loadAvatar( contact, binding.detailsContactBadge, R.dimen.avatar_on_details_screen_size); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index c3b0bcd9d..20ed93c05 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -902,8 +902,7 @@ public class ConversationFragment extends XmppFragment } intent.putExtra("contacts", contacts); intent.putExtra( - EXTRA_ACCOUNT, - conversation.getAccount().getJid().asBareJid().toEscapedString()); + EXTRA_ACCOUNT, conversation.getAccount().getJid().asBareJid().toString()); intent.putExtra("conversation", conversation.getUuid()); startActivityForResult(intent, requestCode); return true; @@ -1562,8 +1561,8 @@ public class ConversationFragment extends XmppFragment intent.setAction(Intent.ACTION_VIEW); intent.putExtra( RtpSessionActivity.EXTRA_ACCOUNT, - id.getAccount().getJid().asBareJid().toEscapedString()); - intent.putExtra(RtpSessionActivity.EXTRA_WITH, id.getWith().toEscapedString()); + id.getAccount().getJid().asBareJid().toString()); + intent.putExtra(RtpSessionActivity.EXTRA_WITH, id.getWith().toString()); if (id instanceof AbstractJingleConnection) { intent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, id.getSessionId()); startActivity(intent); @@ -3570,7 +3569,7 @@ public class ConversationFragment extends XmppFragment + message.getContact() .getJid() .asBareJid() - .toEscapedString()); + .toString()); break; } return true; diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java index 59776a0ab..04853ddd2 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java @@ -29,12 +29,13 @@ package eu.siacs.conversations.ui; +import static androidx.recyclerview.widget.ItemTouchHelper.LEFT; +import static androidx.recyclerview.widget.ItemTouchHelper.RIGHT; + import android.app.Activity; -import android.app.AlertDialog; import android.app.Fragment; import android.content.Intent; import android.graphics.Canvas; -import android.graphics.Paint; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; @@ -44,22 +45,14 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.databinding.DataBindingUtil; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; - -import com.google.android.material.color.MaterialColors; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.snackbar.Snackbar; import com.google.common.collect.Collections2; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; - import eu.siacs.conversations.BuildConfig; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; @@ -77,227 +70,281 @@ import eu.siacs.conversations.ui.util.PendingItem; import eu.siacs.conversations.ui.util.ScrollState; import eu.siacs.conversations.utils.AccountUtils; import eu.siacs.conversations.utils.EasyOnboardingInvite; - -import static androidx.recyclerview.widget.ItemTouchHelper.LEFT; -import static androidx.recyclerview.widget.ItemTouchHelper.RIGHT; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; public class ConversationsOverviewFragment extends XmppFragment { - private static final String STATE_SCROLL_POSITION = ConversationsOverviewFragment.class.getName()+".scroll_state"; - - private final List<Conversation> conversations = new ArrayList<>(); - private final PendingItem<Conversation> swipedConversation = new PendingItem<>(); - private final PendingItem<ScrollState> pendingScrollState = new PendingItem<>(); - private FragmentConversationsOverviewBinding binding; - private ConversationAdapter conversationsAdapter; - private XmppActivity activity; - private final PendingActionHelper pendingActionHelper = new PendingActionHelper(); - - private final ItemTouchHelper.SimpleCallback callback = new ItemTouchHelper.SimpleCallback(0,LEFT|RIGHT) { - @Override - public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) { - return false; - } - - @Override - public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, - float dX, float dY, int actionState, boolean isCurrentlyActive) { - if (viewHolder instanceof ConversationAdapter.ConversationViewHolder conversationViewHolder) { - getDefaultUIUtil().onDraw(c,recyclerView,conversationViewHolder.binding.frame,dX,dY,actionState,isCurrentlyActive); - } - } - - @Override - public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { - if (viewHolder instanceof ConversationAdapter.ConversationViewHolder conversationViewHolder) { - getDefaultUIUtil().clearView(conversationViewHolder.binding.frame); - } - } - - @Override - public float getSwipeEscapeVelocity(final float defaultEscapeVelocity) { - return 32 * defaultEscapeVelocity; - } - - @Override - public void onSwiped(final RecyclerView.ViewHolder viewHolder, final int direction) { - pendingActionHelper.execute(); - int position = viewHolder.getLayoutPosition(); - try { - swipedConversation.push(conversations.get(position)); - } catch (IndexOutOfBoundsException e) { - return; - } - conversationsAdapter.remove(swipedConversation.peek(), position); - activity.xmppConnectionService.markRead(swipedConversation.peek()); - - if (position == 0 && conversationsAdapter.getItemCount() == 0) { - final Conversation c = swipedConversation.pop(); - activity.xmppConnectionService.archiveConversation(c); - return; - } - final boolean formerlySelected = ConversationFragment.getConversation(getActivity()) == swipedConversation.peek(); - if (activity instanceof OnConversationArchived) { - ((OnConversationArchived) activity).onConversationArchived(swipedConversation.peek()); - } - final Conversation c = swipedConversation.peek(); - final int title; - if (c.getMode() == Conversational.MODE_MULTI) { - if (c.getMucOptions().isPrivateAndNonAnonymous()) { - title = R.string.title_undo_swipe_out_group_chat; - } else { - title = R.string.title_undo_swipe_out_channel; - } - } else { - title = R.string.title_undo_swipe_out_chat; - } - - final Snackbar snackbar = Snackbar.make(binding.list, title, 5000) - .setAction(R.string.undo, v -> { - pendingActionHelper.undo(); - Conversation conversation = swipedConversation.pop(); - conversationsAdapter.insert(conversation, position); - if (formerlySelected) { - if (activity instanceof OnConversationSelected) { - ((OnConversationSelected) activity).onConversationSelected(c); - } - } - LinearLayoutManager layoutManager = (LinearLayoutManager) binding.list.getLayoutManager(); - if (position > layoutManager.findLastVisibleItemPosition()) { - binding.list.smoothScrollToPosition(position); - } - }) - .addCallback(new Snackbar.Callback() { - @Override - public void onDismissed(Snackbar transientBottomBar, int event) { - switch (event) { - case DISMISS_EVENT_SWIPE: - case DISMISS_EVENT_TIMEOUT: - pendingActionHelper.execute(); - break; - } - } - }); - - pendingActionHelper.push(() -> { - if (snackbar.isShownOrQueued()) { - snackbar.dismiss(); - } - final Conversation conversation = swipedConversation.pop(); - if(conversation != null){ - if (!conversation.isRead() && conversation.getMode() == Conversation.MODE_SINGLE) { - return; - } - activity.xmppConnectionService.archiveConversation(c); - } - }); - snackbar.show(); - } - }; - - private ItemTouchHelper touchHelper; - - public static Conversation getSuggestion(Activity activity) { - final Conversation exception; - Fragment fragment = activity.getFragmentManager().findFragmentById(R.id.main_fragment); - if (fragment instanceof ConversationsOverviewFragment) { - exception = ((ConversationsOverviewFragment) fragment).swipedConversation.peek(); - } else { - exception = null; - } - return getSuggestion(activity, exception); - } - - public static Conversation getSuggestion(Activity activity, Conversation exception) { - Fragment fragment = activity.getFragmentManager().findFragmentById(R.id.main_fragment); - if (fragment instanceof ConversationsOverviewFragment) { - List<Conversation> conversations = ((ConversationsOverviewFragment) fragment).conversations; - if (conversations.size() > 0) { - Conversation suggestion = conversations.get(0); - if (suggestion == exception) { - if (conversations.size() > 1) { - return conversations.get(1); - } - } else { - return suggestion; - } - } - } - return null; - - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - if (savedInstanceState == null) { - return; - } - pendingScrollState.push(savedInstanceState.getParcelable(STATE_SCROLL_POSITION)); - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - if (activity instanceof XmppActivity) { - this.activity = (XmppActivity) activity; - } else { - throw new IllegalStateException("Trying to attach fragment to activity that is not an XmppActivity"); - } - } - @Override - public void onDestroyView() { - Log.d(Config.LOGTAG,"ConversationsOverviewFragment.onDestroyView()"); - super.onDestroyView(); - this.binding = null; - this.conversationsAdapter = null; - this.touchHelper = null; - } - @Override - public void onDestroy() { - Log.d(Config.LOGTAG,"ConversationsOverviewFragment.onDestroy()"); - super.onDestroy(); - - } - @Override - public void onPause() { - Log.d(Config.LOGTAG,"ConversationsOverviewFragment.onPause()"); - pendingActionHelper.execute(); - super.onPause(); - } - - @Override - public void onDetach() { - super.onDetach(); - this.activity = null; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - } - - @Override - public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - this.binding = DataBindingUtil.inflate(inflater, R.layout.fragment_conversations_overview, container, false); - this.binding.fab.setOnClickListener((view) -> StartConversationActivity.launch(getActivity())); - - this.conversationsAdapter = new ConversationAdapter(this.activity, this.conversations); - this.conversationsAdapter.setConversationClickListener((view, conversation) -> { - if (activity instanceof OnConversationSelected) { - ((OnConversationSelected) activity).onConversationSelected(conversation); - } else { - Log.w(ConversationsOverviewFragment.class.getCanonicalName(), "Activity does not implement OnConversationSelected"); - } - }); - this.binding.list.setAdapter(this.conversationsAdapter); - this.binding.list.setLayoutManager(new LinearLayoutManager(getActivity(),LinearLayoutManager.VERTICAL,false)); - this.binding.list.addOnScrollListener(ExtendedFabSizeChanger.of(binding.fab)); - this.touchHelper = new ItemTouchHelper(this.callback); - this.touchHelper.attachToRecyclerView(this.binding.list); - return binding.getRoot(); - } + private static final String STATE_SCROLL_POSITION = + ConversationsOverviewFragment.class.getName() + ".scroll_state"; + + private final List<Conversation> conversations = new ArrayList<>(); + private final PendingItem<Conversation> swipedConversation = new PendingItem<>(); + private final PendingItem<ScrollState> pendingScrollState = new PendingItem<>(); + private FragmentConversationsOverviewBinding binding; + private ConversationAdapter conversationsAdapter; + private XmppActivity activity; + private final PendingActionHelper pendingActionHelper = new PendingActionHelper(); + + private final ItemTouchHelper.SimpleCallback callback = + new ItemTouchHelper.SimpleCallback(0, LEFT | RIGHT) { + @Override + public boolean onMove( + @NonNull RecyclerView recyclerView, + @NonNull RecyclerView.ViewHolder viewHolder, + @NonNull RecyclerView.ViewHolder target) { + return false; + } + + @Override + public void onChildDraw( + @NonNull Canvas c, + @NonNull RecyclerView recyclerView, + @NonNull RecyclerView.ViewHolder viewHolder, + float dX, + float dY, + int actionState, + boolean isCurrentlyActive) { + if (viewHolder + instanceof + ConversationAdapter.ConversationViewHolder conversationViewHolder) { + getDefaultUIUtil() + .onDraw( + c, + recyclerView, + conversationViewHolder.binding.frame, + dX, + dY, + actionState, + isCurrentlyActive); + } + } + + @Override + public void clearView( + @NonNull RecyclerView recyclerView, + @NonNull RecyclerView.ViewHolder viewHolder) { + if (viewHolder + instanceof + ConversationAdapter.ConversationViewHolder conversationViewHolder) { + getDefaultUIUtil().clearView(conversationViewHolder.binding.frame); + } + } + + @Override + public float getSwipeEscapeVelocity(final float defaultEscapeVelocity) { + return 32 * defaultEscapeVelocity; + } + + @Override + public void onSwiped( + final RecyclerView.ViewHolder viewHolder, final int direction) { + pendingActionHelper.execute(); + int position = viewHolder.getLayoutPosition(); + try { + swipedConversation.push(conversations.get(position)); + } catch (IndexOutOfBoundsException e) { + return; + } + conversationsAdapter.remove(swipedConversation.peek(), position); + activity.xmppConnectionService.markRead(swipedConversation.peek()); + + if (position == 0 && conversationsAdapter.getItemCount() == 0) { + final Conversation c = swipedConversation.pop(); + activity.xmppConnectionService.archiveConversation(c); + return; + } + final boolean formerlySelected = + ConversationFragment.getConversation(getActivity()) + == swipedConversation.peek(); + if (activity instanceof OnConversationArchived) { + ((OnConversationArchived) activity) + .onConversationArchived(swipedConversation.peek()); + } + final Conversation c = swipedConversation.peek(); + final int title; + if (c.getMode() == Conversational.MODE_MULTI) { + if (c.getMucOptions().isPrivateAndNonAnonymous()) { + title = R.string.title_undo_swipe_out_group_chat; + } else { + title = R.string.title_undo_swipe_out_channel; + } + } else { + title = R.string.title_undo_swipe_out_chat; + } + + final Snackbar snackbar = + Snackbar.make(binding.list, title, 5000) + .setAction( + R.string.undo, + v -> { + pendingActionHelper.undo(); + Conversation conversation = + swipedConversation.pop(); + conversationsAdapter.insert(conversation, position); + if (formerlySelected) { + if (activity + instanceof OnConversationSelected) { + ((OnConversationSelected) activity) + .onConversationSelected(c); + } + } + LinearLayoutManager layoutManager = + (LinearLayoutManager) + binding.list.getLayoutManager(); + if (position + > layoutManager + .findLastVisibleItemPosition()) { + binding.list.smoothScrollToPosition(position); + } + }) + .addCallback( + new Snackbar.Callback() { + @Override + public void onDismissed( + Snackbar transientBottomBar, int event) { + switch (event) { + case DISMISS_EVENT_SWIPE: + case DISMISS_EVENT_TIMEOUT: + pendingActionHelper.execute(); + break; + } + } + }); + + pendingActionHelper.push( + () -> { + if (snackbar.isShownOrQueued()) { + snackbar.dismiss(); + } + final Conversation conversation = swipedConversation.pop(); + if (conversation != null) { + if (!conversation.isRead() + && conversation.getMode() == Conversation.MODE_SINGLE) { + return; + } + activity.xmppConnectionService.archiveConversation(c); + } + }); + snackbar.show(); + } + }; + + private ItemTouchHelper touchHelper; + + public static Conversation getSuggestion(Activity activity) { + final Conversation exception; + Fragment fragment = activity.getFragmentManager().findFragmentById(R.id.main_fragment); + if (fragment instanceof ConversationsOverviewFragment) { + exception = ((ConversationsOverviewFragment) fragment).swipedConversation.peek(); + } else { + exception = null; + } + return getSuggestion(activity, exception); + } + + public static Conversation getSuggestion(Activity activity, Conversation exception) { + Fragment fragment = activity.getFragmentManager().findFragmentById(R.id.main_fragment); + if (fragment instanceof ConversationsOverviewFragment) { + List<Conversation> conversations = + ((ConversationsOverviewFragment) fragment).conversations; + if (conversations.size() > 0) { + Conversation suggestion = conversations.get(0); + if (suggestion == exception) { + if (conversations.size() > 1) { + return conversations.get(1); + } + } else { + return suggestion; + } + } + } + return null; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + if (savedInstanceState == null) { + return; + } + pendingScrollState.push(savedInstanceState.getParcelable(STATE_SCROLL_POSITION)); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + if (activity instanceof XmppActivity) { + this.activity = (XmppActivity) activity; + } else { + throw new IllegalStateException( + "Trying to attach fragment to activity that is not an XmppActivity"); + } + } + + @Override + public void onDestroyView() { + Log.d(Config.LOGTAG, "ConversationsOverviewFragment.onDestroyView()"); + super.onDestroyView(); + this.binding = null; + this.conversationsAdapter = null; + this.touchHelper = null; + } + + @Override + public void onDestroy() { + Log.d(Config.LOGTAG, "ConversationsOverviewFragment.onDestroy()"); + super.onDestroy(); + } + + @Override + public void onPause() { + Log.d(Config.LOGTAG, "ConversationsOverviewFragment.onPause()"); + pendingActionHelper.execute(); + super.onPause(); + } + + @Override + public void onDetach() { + super.onDetach(); + this.activity = null; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public View onCreateView( + final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + this.binding = + DataBindingUtil.inflate( + inflater, R.layout.fragment_conversations_overview, container, false); + this.binding.fab.setOnClickListener( + (view) -> StartConversationActivity.launch(getActivity())); + + this.conversationsAdapter = new ConversationAdapter(this.activity, this.conversations); + this.conversationsAdapter.setConversationClickListener( + (view, conversation) -> { + if (activity instanceof OnConversationSelected) { + ((OnConversationSelected) activity).onConversationSelected(conversation); + } else { + Log.w( + ConversationsOverviewFragment.class.getCanonicalName(), + "Activity does not implement OnConversationSelected"); + } + }); + this.binding.list.setAdapter(this.conversationsAdapter); + this.binding.list.setLayoutManager( + new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false)); + this.binding.list.addOnScrollListener(ExtendedFabSizeChanger.of(binding.fab)); + this.touchHelper = new ItemTouchHelper(this.callback); + this.touchHelper.attachToRecyclerView(this.binding.list); + return binding.getRoot(); + } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) { @@ -313,114 +360,131 @@ public class ConversationsOverviewFragment extends XmppFragment { && QuickConversationsService.isPlayStoreFlavor()); } - @Override - public void onBackendConnected() { - refresh(); - } - - @Override - public void onSaveInstanceState(Bundle bundle) { - super.onSaveInstanceState(bundle); - ScrollState scrollState = getScrollState(); - if (scrollState != null) { - bundle.putParcelable(STATE_SCROLL_POSITION, scrollState); - } - } - - private ScrollState getScrollState() { - if (this.binding == null) { - return null; - } - LinearLayoutManager layoutManager = (LinearLayoutManager) this.binding.list.getLayoutManager(); - int position = layoutManager.findFirstVisibleItemPosition(); - final View view = this.binding.list.getChildAt(0); - if (view != null) { - return new ScrollState(position,view.getTop()); - } else { - return new ScrollState(position, 0); - } - } - - @Override - public void onStart() { - super.onStart(); - Log.d(Config.LOGTAG, "ConversationsOverviewFragment.onStart()"); - if (activity.xmppConnectionService != null) { - refresh(); - } - } - - @Override - public void onResume() { - super.onResume(); - Log.d(Config.LOGTAG, "ConversationsOverviewFragment.onResume()"); - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - if (MenuDoubleTabUtil.shouldIgnoreTap()) { - return false; - } - switch (item.getItemId()) { - case R.id.action_search: - startActivity(new Intent(getActivity(), SearchActivity.class)); - return true; - case R.id.action_easy_invite: - selectAccountToStartEasyInvite(); - return true; - } - return super.onOptionsItemSelected(item); - } - - private void selectAccountToStartEasyInvite() { - final List<Account> accounts = EasyOnboardingInvite.getSupportingAccounts(activity.xmppConnectionService); - if (accounts.isEmpty()) { - //This can technically happen if opening the menu item races with accounts reconnecting or something - Toast.makeText(getActivity(),R.string.no_active_accounts_support_this, Toast.LENGTH_LONG).show(); - } else if (accounts.size() == 1) { - openEasyInviteScreen(accounts.get(0)); - } else { - final AtomicReference<Account> selectedAccount = new AtomicReference<>(accounts.get(0)); - final MaterialAlertDialogBuilder alertDialogBuilder = new MaterialAlertDialogBuilder(activity); - alertDialogBuilder.setTitle(R.string.choose_account); - final String[] asStrings = Collections2.transform(accounts, a -> a.getJid().asBareJid().toEscapedString()).toArray(new String[0]); - alertDialogBuilder.setSingleChoiceItems(asStrings, 0, (dialog, which) -> selectedAccount.set(accounts.get(which))); - alertDialogBuilder.setNegativeButton(R.string.cancel, null); - alertDialogBuilder.setPositiveButton(R.string.ok, (dialog, which) -> openEasyInviteScreen(selectedAccount.get())); - alertDialogBuilder.create().show(); - } - } - - private void openEasyInviteScreen(final Account account) { - EasyOnboardingInviteActivity.launch(account, activity); - } - - @Override - void refresh() { - if (this.binding == null || this.activity == null) { - Log.d(Config.LOGTAG,"ConversationsOverviewFragment.refresh() skipped updated because view binding or activity was null"); - return; - } - this.activity.xmppConnectionService.populateWithOrderedConversations(this.conversations); - Conversation removed = this.swipedConversation.peek(); - if (removed != null) { - if (removed.isRead()) { - this.conversations.remove(removed); - } else { - pendingActionHelper.execute(); - } - } - this.conversationsAdapter.notifyDataSetChanged(); - ScrollState scrollState = pendingScrollState.pop(); - if (scrollState != null) { - setScrollPosition(scrollState); - } - } - - private void setScrollPosition(ScrollState scrollPosition) { - if (scrollPosition != null) { - LinearLayoutManager layoutManager = (LinearLayoutManager) binding.list.getLayoutManager(); - layoutManager.scrollToPositionWithOffset(scrollPosition.position, scrollPosition.offset); - } - } + @Override + public void onBackendConnected() { + refresh(); + } + + @Override + public void onSaveInstanceState(Bundle bundle) { + super.onSaveInstanceState(bundle); + ScrollState scrollState = getScrollState(); + if (scrollState != null) { + bundle.putParcelable(STATE_SCROLL_POSITION, scrollState); + } + } + + private ScrollState getScrollState() { + if (this.binding == null) { + return null; + } + LinearLayoutManager layoutManager = + (LinearLayoutManager) this.binding.list.getLayoutManager(); + int position = layoutManager.findFirstVisibleItemPosition(); + final View view = this.binding.list.getChildAt(0); + if (view != null) { + return new ScrollState(position, view.getTop()); + } else { + return new ScrollState(position, 0); + } + } + + @Override + public void onStart() { + super.onStart(); + Log.d(Config.LOGTAG, "ConversationsOverviewFragment.onStart()"); + if (activity.xmppConnectionService != null) { + refresh(); + } + } + + @Override + public void onResume() { + super.onResume(); + Log.d(Config.LOGTAG, "ConversationsOverviewFragment.onResume()"); + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + if (MenuDoubleTabUtil.shouldIgnoreTap()) { + return false; + } + switch (item.getItemId()) { + case R.id.action_search: + startActivity(new Intent(getActivity(), SearchActivity.class)); + return true; + case R.id.action_easy_invite: + selectAccountToStartEasyInvite(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void selectAccountToStartEasyInvite() { + final List<Account> accounts = + EasyOnboardingInvite.getSupportingAccounts(activity.xmppConnectionService); + if (accounts.isEmpty()) { + // This can technically happen if opening the menu item races with accounts reconnecting + // or something + Toast.makeText( + getActivity(), + R.string.no_active_accounts_support_this, + Toast.LENGTH_LONG) + .show(); + } else if (accounts.size() == 1) { + openEasyInviteScreen(accounts.get(0)); + } else { + final AtomicReference<Account> selectedAccount = new AtomicReference<>(accounts.get(0)); + final MaterialAlertDialogBuilder alertDialogBuilder = + new MaterialAlertDialogBuilder(activity); + alertDialogBuilder.setTitle(R.string.choose_account); + final String[] asStrings = + Collections2.transform(accounts, a -> a.getJid().asBareJid().toString()) + .toArray(new String[0]); + alertDialogBuilder.setSingleChoiceItems( + asStrings, 0, (dialog, which) -> selectedAccount.set(accounts.get(which))); + alertDialogBuilder.setNegativeButton(R.string.cancel, null); + alertDialogBuilder.setPositiveButton( + R.string.ok, (dialog, which) -> openEasyInviteScreen(selectedAccount.get())); + alertDialogBuilder.create().show(); + } + } + + private void openEasyInviteScreen(final Account account) { + EasyOnboardingInviteActivity.launch(account, activity); + } + + @Override + void refresh() { + if (this.binding == null || this.activity == null) { + Log.d( + Config.LOGTAG, + "ConversationsOverviewFragment.refresh() skipped updated because view binding" + + " or activity was null"); + return; + } + this.activity.xmppConnectionService.populateWithOrderedConversations(this.conversations); + Conversation removed = this.swipedConversation.peek(); + if (removed != null) { + if (removed.isRead()) { + this.conversations.remove(removed); + } else { + pendingActionHelper.execute(); + } + } + this.conversationsAdapter.notifyDataSetChanged(); + ScrollState scrollState = pendingScrollState.pop(); + if (scrollState != null) { + setScrollPosition(scrollState); + } + } + + private void setScrollPosition(ScrollState scrollPosition) { + if (scrollPosition != null) { + LinearLayoutManager layoutManager = + (LinearLayoutManager) binding.list.getLayoutManager(); + layoutManager.scrollToPositionWithOffset( + scrollPosition.position, scrollPosition.offset); + } + } } diff --git a/src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java b/src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java index b20db451d..b12aba750 100644 --- a/src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java +++ b/src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java @@ -11,18 +11,11 @@ import android.text.TextWatcher; import android.view.View; import android.widget.AdapterView; import android.widget.Button; - 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.Collection; -import java.util.List; - import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.DialogCreatePublicChannelBinding; import eu.siacs.conversations.entities.Account; @@ -33,10 +26,14 @@ import eu.siacs.conversations.ui.util.DelayedHintHelper; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.XmppConnection; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; public class CreatePublicChannelDialog extends DialogFragment implements OnBackendConnected { - private static final char[] FORBIDDEN = new char[]{'\u0022','&','\'','/',':','<','>','@'}; + private static final char[] FORBIDDEN = + new char[] {'\u0022', '&', '\'', '/', ':', '<', '>', '@'}; private static final String ACCOUNTS_LIST_KEY = "activated_accounts_list"; private CreatePublicChannelDialogListener mListener; @@ -62,48 +59,56 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - jidWasModified = savedInstanceState != null && savedInstanceState.getBoolean("jid_was_modified_false", false); - nameEntered = savedInstanceState != null && savedInstanceState.getBoolean("name_entered", false); - final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity()); + jidWasModified = + savedInstanceState != null + && savedInstanceState.getBoolean("jid_was_modified_false", false); + nameEntered = + savedInstanceState != null && savedInstanceState.getBoolean("name_entered", false); + final MaterialAlertDialogBuilder builder = + new MaterialAlertDialogBuilder(requireActivity()); builder.setTitle(R.string.create_public_channel); - final DialogCreatePublicChannelBinding binding = DataBindingUtil.inflate(getActivity().getLayoutInflater(), R.layout.dialog_create_public_channel, null, false); - binding.account.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { - updateJidSuggestion(binding); - } - - @Override - public void onNothingSelected(AdapterView<?> parent) { - - } - }); - binding.jid.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - - } - - @Override - public void afterTextChanged(Editable s) { - if (skipTetxWatcher) { - return; - } - if (jidWasModified) { - jidWasModified = !TextUtils.isEmpty(s); - } else { - jidWasModified = !s.toString().equals(getJidSuggestion(binding)); - } - } - }); - updateInputs(binding,false); + final DialogCreatePublicChannelBinding binding = + DataBindingUtil.inflate( + getActivity().getLayoutInflater(), + R.layout.dialog_create_public_channel, + null, + false); + binding.account.setOnItemSelectedListener( + new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected( + AdapterView<?> parent, View view, int position, long id) { + updateJidSuggestion(binding); + } + + @Override + public void onNothingSelected(AdapterView<?> parent) {} + }); + binding.jid.addTextChangedListener( + new TextWatcher() { + @Override + public void beforeTextChanged( + CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + + @Override + public void afterTextChanged(Editable s) { + if (skipTetxWatcher) { + return; + } + if (jidWasModified) { + jidWasModified = !TextUtils.isEmpty(s); + } else { + jidWasModified = !s.toString().equals(getJidSuggestion(binding)); + } + } + }); + updateInputs(binding, false); ArrayList<String> mActivatedAccounts = getArguments().getStringArrayList(ACCOUNTS_LIST_KEY); - StartConversationActivity.populateAccountSpinner(getActivity(), mActivatedAccounts, binding.account); + StartConversationActivity.populateAccountSpinner( + getActivity(), mActivatedAccounts, binding.account); builder.setView(binding.getRoot()); builder.setPositiveButton(nameEntered ? R.string.create : R.string.next, null); builder.setNegativeButton(nameEntered ? R.string.back : R.string.cancel, null); @@ -111,14 +116,18 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke this.knownHostsAdapter = new KnownHostsAdapter(getActivity(), R.layout.item_autocomplete); binding.jid.setAdapter(knownHostsAdapter); final AlertDialog dialog = builder.create(); - binding.groupChatName.setOnEditorActionListener((v, actionId, event) -> { - submit(dialog, binding); - return true; - }); - dialog.setOnShowListener(dialogInterface -> { - dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener(v -> goBack(dialog, binding)); - dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> submit(dialog, binding)); - }); + binding.groupChatName.setOnEditorActionListener( + (v, actionId, event) -> { + submit(dialog, binding); + return true; + }); + dialog.setOnShowListener( + dialogInterface -> { + dialog.getButton(DialogInterface.BUTTON_NEGATIVE) + .setOnClickListener(v -> goBack(dialog, binding)); + dialog.getButton(DialogInterface.BUTTON_POSITIVE) + .setOnClickListener(v -> submit(dialog, binding)); + }); return dialog; } @@ -134,13 +143,15 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke @Override public void onSaveInstanceState(Bundle outState) { - outState.putBoolean("jid_was_modified",jidWasModified); + outState.putBoolean("jid_was_modified", jidWasModified); outState.putBoolean("name_entered", nameEntered); super.onSaveInstanceState(outState); } private static String getJidSuggestion(final DialogCreatePublicChannelBinding binding) { - final Account account = StartConversationActivity.getSelectedAccount(binding.getRoot().getContext(), binding.account); + final Account account = + StartConversationActivity.getSelectedAccount( + binding.getRoot().getContext(), binding.account); final XmppConnection connection = account == null ? null : account.getXmppConnection(); if (connection == null) { return ""; @@ -156,18 +167,18 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke return ""; } else { try { - return Jid.of(localpart, domain, null).toEscapedString(); + return Jid.of(localpart, domain, null).toString(); } catch (IllegalArgumentException e) { - return Jid.of(CryptoHelper.pronounceable(), domain, null).toEscapedString(); + return Jid.of(CryptoHelper.pronounceable(), domain, null).toString(); } } } private static String clean(String name) { - for(char c : FORBIDDEN) { - name = name.replace(String.valueOf(c),""); + for (char c : FORBIDDEN) { + name = name.replace(String.valueOf(c), ""); } - return name.replaceAll("\\s+","-"); + return name.replaceAll("\\s+", "-"); } private void goBack(AlertDialog dialog, DialogCreatePublicChannelBinding binding) { @@ -189,22 +200,26 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke if (nameEntered) { binding.nameLayout.setError(null); if (address.isEmpty()) { - binding.xmppAddressLayout.setError(context.getText(R.string.please_enter_xmpp_address)); + binding.xmppAddressLayout.setError( + context.getText(R.string.please_enter_xmpp_address)); } else { final Jid jid; try { - jid = Jid.ofEscaped(address); - } catch (IllegalArgumentException e) { + jid = Jid.ofUserInput(address); + } catch (final IllegalArgumentException e) { binding.xmppAddressLayout.setError(context.getText(R.string.invalid_jid)); return; } - final Account account = StartConversationActivity.getSelectedAccount(context, binding.account); + final Account account = + StartConversationActivity.getSelectedAccount(context, binding.account); if (account == null) { return; } - final XmppConnectionService service = ((XmppActivity )context).xmppConnectionService; + final XmppConnectionService service = + ((XmppActivity) context).xmppConnectionService; if (service != null && service.findFirstMuc(jid) != null) { - binding.xmppAddressLayout.setError(context.getString(R.string.channel_already_exists)); + binding.xmppAddressLayout.setError( + context.getString(R.string.channel_already_exists)); return; } mListener.onCreatePublicChannel(account, name, jid); @@ -214,7 +229,7 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke binding.xmppAddressLayout.setError(null); if (name.isEmpty()) { binding.nameLayout.setError(context.getText(R.string.please_enter_name)); - } else if (StartConversationActivity.isValidJid(name)){ + } else if (StartConversationActivity.isValidJid(name)) { binding.nameLayout.setError(context.getText(R.string.this_is_an_xmpp_address)); } else { binding.nameLayout.setError(null); @@ -227,8 +242,8 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke } } - - private void updateInputs(final DialogCreatePublicChannelBinding binding, final boolean requestFocus) { + private void updateInputs( + final DialogCreatePublicChannelBinding binding, final boolean requestFocus) { binding.xmppAddressLayout.setVisibility(nameEntered ? View.VISIBLE : View.GONE); binding.nameLayout.setVisibility(nameEntered ? View.GONE : View.VISIBLE); if (!requestFocus) { @@ -256,7 +271,8 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke private void refreshKnownHosts() { Activity activity = getActivity(); if (activity instanceof XmppActivity) { - Collection<String> hosts = ((XmppActivity) activity).xmppConnectionService.getKnownConferenceHosts(); + Collection<String> hosts = + ((XmppActivity) activity).xmppConnectionService.getKnownConferenceHosts(); this.knownHostsAdapter.refresh(hosts); } } @@ -271,8 +287,8 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke try { mListener = (CreatePublicChannelDialogListener) context; } catch (ClassCastException e) { - throw new ClassCastException(context.toString() - + " must implement CreateConferenceDialogListener"); + throw new ClassCastException( + context.toString() + " must implement CreateConferenceDialogListener"); } } @@ -280,7 +296,8 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke public void onStart() { super.onStart(); final Activity activity = getActivity(); - if (activity instanceof XmppActivity && ((XmppActivity) activity).xmppConnectionService != null) { + if (activity instanceof XmppActivity + && ((XmppActivity) activity).xmppConnectionService != null) { refreshKnownHosts(); } } diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index 61c76eb6e..14f732957 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -29,19 +29,16 @@ import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.databinding.DataBindingUtil; import androidx.lifecycle.Lifecycle; - import com.google.android.material.color.MaterialColors; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.textfield.TextInputLayout; import com.google.common.base.CharMatcher; import com.google.common.base.Strings; - import eu.siacs.conversations.AppSettings; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; @@ -79,15 +76,12 @@ import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.XmppConnection.Features; import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.pep.Avatar; - -import okhttp3.HttpUrl; - -import org.openintents.openpgp.util.OpenPgpUtils; - import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import okhttp3.HttpUrl; +import org.openintents.openpgp.util.OpenPgpUtils; public class EditAccountActivity extends OmemoActivity implements OnAccountUpdate, @@ -144,8 +138,7 @@ public class EditAccountActivity extends OmemoActivity new Intent( getApplicationContext(), PublishProfilePictureActivity.class); - intent.putExtra( - EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toString()); startActivity(intent); } } @@ -259,12 +252,12 @@ public class EditAccountActivity extends OmemoActivity try { if (mUsernameMode) { jid = - Jid.ofEscaped( + Jid.of( binding.accountJid.getText().toString(), getUserModeDomain(), null); } else { - jid = Jid.ofEscaped(binding.accountJid.getText().toString()); + jid = Jid.ofUserInput(binding.accountJid.getText().toString()); Resolver.checkDomain(jid); } } catch (final NullPointerException | IllegalArgumentException e) { @@ -539,15 +532,13 @@ public class EditAccountActivity extends OmemoActivity if (wasFirstAccount) { intent.putExtra("init", true); } - intent.putExtra( - EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toString()); } else { intent = new Intent( getApplicationContext(), PublishProfilePictureActivity.class); - intent.putExtra( - EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toString()); intent.putExtra("setup", true); } if (wasFirstAccount) { @@ -700,9 +691,9 @@ public class EditAccountActivity extends OmemoActivity protected boolean jidEdited() { final String unmodified; if (mUsernameMode) { - unmodified = this.mAccount.getJid().getEscapedLocal(); + unmodified = this.mAccount.getJid().getLocal(); } else { - unmodified = this.mAccount.getJid().asBareJid().toEscapedString(); + unmodified = this.mAccount.getJid().asBareJid().toString(); } return !unmodified.equals(this.binding.accountJid.getText().toString()); } @@ -824,7 +815,7 @@ public class EditAccountActivity extends OmemoActivity final Intent intent = getIntent(); if (intent != null) { try { - this.jidToEdit = Jid.ofEscaped(intent.getStringExtra("jid")); + this.jidToEdit = Jid.of(intent.getStringExtra("jid")); } catch (final IllegalArgumentException | NullPointerException ignored) { this.jidToEdit = null; } @@ -929,8 +920,7 @@ public class EditAccountActivity extends OmemoActivity @Override public void onSaveInstanceState(@NonNull final Bundle savedInstanceState) { if (mAccount != null) { - savedInstanceState.putString( - "account", mAccount.getJid().asBareJid().toEscapedString()); + savedInstanceState.putString("account", mAccount.getJid().asBareJid().toString()); savedInstanceState.putBoolean("initMode", mInitMode); savedInstanceState.putBoolean( "showMoreTable", binding.serverInfoMore.getVisibility() == View.VISIBLE); @@ -943,8 +933,7 @@ public class EditAccountActivity extends OmemoActivity if (mSavedInstanceAccount != null) { try { this.mAccount = - xmppConnectionService.findAccountByJid( - Jid.ofEscaped(mSavedInstanceAccount)); + xmppConnectionService.findAccountByJid(Jid.of(mSavedInstanceAccount)); this.mInitMode = mSavedInstanceInit; init = false; } catch (IllegalArgumentException e) { @@ -1010,7 +999,7 @@ public class EditAccountActivity extends OmemoActivity break; case R.id.action_show_block_list: final Intent showBlocklistIntent = new Intent(this, BlocklistActivity.class); - showBlocklistIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toEscapedString()); + showBlocklistIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString()); startActivity(showBlocklistIntent); break; case R.id.action_server_info_show_more: @@ -1070,7 +1059,7 @@ public class EditAccountActivity extends OmemoActivity private void gotoChangePassword() { final Intent changePasswordIntent = new Intent(this, ChangePasswordActivity.class); - changePasswordIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toEscapedString()); + changePasswordIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString()); startActivity(changePasswordIntent); } @@ -1167,15 +1156,12 @@ public class EditAccountActivity extends OmemoActivity if (init) { this.binding.accountJid.getEditableText().clear(); if (mUsernameMode) { - this.binding - .accountJid - .getEditableText() - .append(this.mAccount.getJid().getEscapedLocal()); + this.binding.accountJid.getEditableText().append(this.mAccount.getJid().getLocal()); } else { this.binding .accountJid .getEditableText() - .append(this.mAccount.getJid().asBareJid().toEscapedString()); + .append(this.mAccount.getJid().asBareJid().toString()); } this.binding.accountPassword.getEditableText().clear(); this.binding.accountPassword.getEditableText().append(this.mAccount.getPassword()); diff --git a/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java b/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java index 787203f0c..bcd2432da 100644 --- a/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java +++ b/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java @@ -7,21 +7,12 @@ import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.widget.ArrayAdapter; - 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.common.base.Strings; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.DialogEnterJidBinding; import eu.siacs.conversations.services.XmppConnectionService; @@ -29,6 +20,11 @@ import eu.siacs.conversations.ui.adapter.KnownHostsAdapter; import eu.siacs.conversations.ui.interfaces.OnBackendConnected; import eu.siacs.conversations.ui.util.DelayedHintHelper; import eu.siacs.conversations.xmpp.Jid; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; public class EnterJidDialog extends DialogFragment implements OnBackendConnected, TextWatcher { @@ -95,10 +91,15 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { final var arguments = getArguments(); - final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity()); + final MaterialAlertDialogBuilder builder = + new MaterialAlertDialogBuilder(requireActivity()); builder.setTitle(arguments.getString(TITLE_KEY)); binding = - DataBindingUtil.inflate(requireActivity().getLayoutInflater(), R.layout.dialog_enter_jid, null, false); + DataBindingUtil.inflate( + requireActivity().getLayoutInflater(), + R.layout.dialog_enter_jid, + null, + false); this.knownHostsAdapter = new KnownHostsAdapter(getActivity(), R.layout.item_autocomplete); binding.jid.setAdapter(this.knownHostsAdapter); binding.jid.addTextChangedListener(this); @@ -124,7 +125,8 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected binding.account); } else { final ArrayAdapter<String> adapter = - new ArrayAdapter<>(requireActivity(), R.layout.item_autocomplete, new String[] {account}); + new ArrayAdapter<>( + requireActivity(), R.layout.item_autocomplete, new String[] {account}); binding.account.setText(account); binding.account.setEnabled(false); adapter.setDropDownViewResource(R.layout.item_autocomplete); @@ -136,8 +138,7 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected builder.setPositiveButton(getArguments().getString(POSITIVE_BUTTON_KEY), null); this.dialog = builder.create(); - View.OnClickListener dialogOnClick = - v -> handleEnter(binding, account); + View.OnClickListener dialogOnClick = v -> handleEnter(binding, account); binding.jid.setOnEditorActionListener( (v, actionId, event) -> { @@ -156,13 +157,13 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected return; } try { - accountJid = Jid.ofEscaped((String) binding.account.getEditableText().toString()); + accountJid = Jid.of(binding.account.getEditableText().toString()); } catch (final IllegalArgumentException e) { return; } final Jid contactJid; try { - contactJid = Jid.ofEscaped(binding.jid.getText().toString().trim()); + contactJid = Jid.ofUserInput(binding.jid.getText().toString().trim()); } catch (final IllegalArgumentException e) { binding.jidLayout.setError(getActivity().getString(R.string.invalid_jid)); return; @@ -176,7 +177,7 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected issuedWarning = true; return; } - if (suspiciousSubDomain(contactJid.getDomain().toEscapedString())) { + if (suspiciousSubDomain(contactJid.getDomain().toString())) { binding.jidLayout.setError( getActivity().getString(R.string.this_looks_like_channel)); dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.add_anway); diff --git a/src/main/java/eu/siacs/conversations/ui/MediaBrowserActivity.java b/src/main/java/eu/siacs/conversations/ui/MediaBrowserActivity.java index ac5b07e77..58d46fd76 100644 --- a/src/main/java/eu/siacs/conversations/ui/MediaBrowserActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/MediaBrowserActivity.java @@ -3,11 +3,7 @@ package eu.siacs.conversations.ui; import android.content.Context; import android.content.Intent; import android.os.Bundle; - import androidx.databinding.DataBindingUtil; - -import java.util.List; - import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityMediaBrowserBinding; import eu.siacs.conversations.entities.Account; @@ -18,6 +14,7 @@ import eu.siacs.conversations.ui.interfaces.OnMediaLoaded; import eu.siacs.conversations.ui.util.Attachment; import eu.siacs.conversations.ui.util.GridManager; import eu.siacs.conversations.xmpp.Jid; +import java.util.List; public class MediaBrowserActivity extends XmppActivity implements OnMediaLoaded { @@ -28,20 +25,17 @@ public class MediaBrowserActivity extends XmppActivity implements OnMediaLoaded @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - this.binding = DataBindingUtil.setContentView(this,R.layout.activity_media_browser); + this.binding = DataBindingUtil.setContentView(this, R.layout.activity_media_browser); Activities.setStatusAndNavigationBarColors(this, binding.getRoot()); setSupportActionBar(binding.toolbar); configureActionBar(getSupportActionBar()); mMediaAdapter = new MediaAdapter(this, R.dimen.media_size); this.binding.media.setAdapter(mMediaAdapter); GridManager.setupLayoutManager(this, this.binding.media, R.dimen.browser_media_size); - } @Override - protected void refreshUiReal() { - - } + protected void refreshUiReal() {} @Override protected void onBackendConnected() { @@ -49,29 +43,30 @@ public class MediaBrowserActivity extends XmppActivity implements OnMediaLoaded String account = intent == null ? null : intent.getStringExtra("account"); String jid = intent == null ? null : intent.getStringExtra("jid"); if (account != null && jid != null) { - xmppConnectionService.getAttachments(account, Jid.ofEscaped(jid), 0, this); + xmppConnectionService.getAttachments(account, Jid.of(jid), 0, this); } } public static void launch(Context context, Contact contact) { - launch(context, contact.getAccount(), contact.getJid().asBareJid().toEscapedString()); + launch(context, contact.getAccount(), contact.getJid().asBareJid().toString()); } public static void launch(Context context, Conversation conversation) { - launch(context, conversation.getAccount(), conversation.getJid().asBareJid().toEscapedString()); + launch(context, conversation.getAccount(), conversation.getJid().asBareJid().toString()); } private static void launch(Context context, Account account, String jid) { final Intent intent = new Intent(context, MediaBrowserActivity.class); - intent.putExtra("account",account.getUuid()); - intent.putExtra("jid",jid); + intent.putExtra("account", account.getUuid()); + intent.putExtra("jid", jid); context.startActivity(intent); } @Override public void onMediaLoaded(List<Attachment> attachments) { - runOnUiThread(()->{ - mMediaAdapter.setAttachments(attachments); - }); + runOnUiThread( + () -> { + mMediaAdapter.setAttachments(attachments); + }); } } diff --git a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java index dafbbabfc..ebc8225d8 100644 --- a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java @@ -60,8 +60,7 @@ public class PublishProfilePictureActivity extends XmppActivity getApplicationContext(), StartConversationActivity.class); StartConversationActivity.addInviteUri(intent, getIntent()); intent.putExtra("init", true); - intent.putExtra( - EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toString()); startActivity(intent); } Toast.makeText( @@ -118,8 +117,7 @@ public class PublishProfilePictureActivity extends XmppActivity } StartConversationActivity.addInviteUri(intent, getIntent()); if (account != null) { - intent.putExtra( - EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toString()); } startActivity(intent); } diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index 89068306d..8435a4da1 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -1,7 +1,6 @@ package eu.siacs.conversations.ui; import static eu.siacs.conversations.utils.PermissionUtils.getFirstDenied; - import static java.util.Arrays.asList; import android.Manifest; @@ -25,13 +24,11 @@ import android.view.MenuItem; import android.view.View; import android.view.WindowManager; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.StringRes; import androidx.databinding.DataBindingUtil; - import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.common.base.Optional; import com.google.common.base.Preconditions; @@ -41,7 +38,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityRtpSessionBinding; @@ -65,16 +61,14 @@ import eu.siacs.conversations.xmpp.jingle.Media; import eu.siacs.conversations.xmpp.jingle.OngoingRtpSession; import eu.siacs.conversations.xmpp.jingle.RtpCapability; import eu.siacs.conversations.xmpp.jingle.RtpEndUserState; - -import org.webrtc.RendererCommon; -import org.webrtc.SurfaceViewRenderer; -import org.webrtc.VideoTrack; - import java.lang.ref.WeakReference; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; +import org.webrtc.RendererCommon; +import org.webrtc.SurfaceViewRenderer; +import org.webrtc.VideoTrack; public class RtpSessionActivity extends XmppActivity implements XmppConnectionService.OnJingleRtpConnectionUpdate, @@ -300,7 +294,7 @@ public class RtpSessionActivity extends XmppActivity final String action = intent.getAction(); final String lastAction = intent.getStringExtra(EXTRA_LAST_ACTION); final Account account = extractAccount(intent); - final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH)); + final Jid with = Jid.of(intent.getStringExtra(EXTRA_WITH)); final String state = intent.getStringExtra(EXTRA_LAST_REPORTED_STATE); if (!Intent.ACTION_VIEW.equals(action) || state == null @@ -504,7 +498,7 @@ public class RtpSessionActivity extends XmppActivity Log.d(Config.LOGTAG, "initializeWithIntent(" + event + "," + action + ")"); final Account account = extractAccount(intent); final var extraWith = intent.getStringExtra(EXTRA_WITH); - final Jid with = Strings.isNullOrEmpty(extraWith) ? null : Jid.ofEscaped(extraWith); + final Jid with = Strings.isNullOrEmpty(extraWith) ? null : Jid.of(extraWith); if (with == null || account == null) { Log.e(Config.LOGTAG, "intent is missing extras (account or with)"); return; @@ -573,7 +567,7 @@ public class RtpSessionActivity extends XmppActivity binding.with.setText(contact.getDisplayName()); if (Arrays.asList(RtpEndUserState.INCOMING_CALL, RtpEndUserState.ACCEPTING_CALL) .contains(state)) { - binding.withJid.setText(contact.getJid().asBareJid().toEscapedString()); + binding.withJid.setText(contact.getJid().asBareJid().toString()); binding.withJid.setVisibility(View.VISIBLE); } else { binding.withJid.setVisibility(View.GONE); @@ -776,7 +770,8 @@ public class RtpSessionActivity extends XmppActivity .getTerminalSessionState(with, sessionId); if (terminatedRtpSession == null) { throw new IllegalStateException( - "failed to initialize activity with running rtp session. session not found"); + "failed to initialize activity with running rtp session. session not" + + " found"); } initializeWithTerminatedSessionState(account, with, terminatedRtpSession); return true; @@ -837,8 +832,8 @@ public class RtpSessionActivity extends XmppActivity private void resetIntent(final Account account, final Jid with, final String sessionId) { final Intent intent = new Intent(Intent.ACTION_VIEW); - intent.putExtra(EXTRA_ACCOUNT, account.getJid().toEscapedString()); - intent.putExtra(EXTRA_WITH, with.toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().toString()); + intent.putExtra(EXTRA_WITH, with.toString()); intent.putExtra(EXTRA_SESSION_ID, sessionId); setIntent(intent); } @@ -895,10 +890,12 @@ public class RtpSessionActivity extends XmppActivity case RETRACTED -> setTitle(R.string.rtp_state_retracted); case APPLICATION_ERROR -> setTitle(R.string.rtp_state_application_failure); case SECURITY_ERROR -> setTitle(R.string.rtp_state_security_error); - case ENDED -> throw new IllegalStateException( - "Activity should have called finishAndReleaseWakeLock();"); - default -> throw new IllegalStateException( - String.format("State %s has not been handled in UI", state)); + case ENDED -> + throw new IllegalStateException( + "Activity should have called finishAndReleaseWakeLock();"); + default -> + throw new IllegalStateException( + String.format("State %s has not been handled in UI", state)); } } @@ -932,9 +929,7 @@ public class RtpSessionActivity extends XmppActivity final Account account = contact == null ? getWith().getAccount() : contact.getAccount(); binding.usingAccount.setVisibility(View.VISIBLE); binding.usingAccount.setText( - getString( - R.string.using_account, - account.getJid().asBareJid().toEscapedString())); + getString(R.string.using_account, account.getJid().asBareJid().toString())); } else { binding.usingAccount.setVisibility(View.GONE); binding.contactPhoto.setVisibility(View.GONE); @@ -1430,12 +1425,12 @@ public class RtpSessionActivity extends XmppActivity private void retry(final View view) { final Intent intent = getIntent(); final Account account = extractAccount(intent); - final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH)); + final Jid with = Jid.of(intent.getStringExtra(EXTRA_WITH)); final String lastAction = intent.getStringExtra(EXTRA_LAST_ACTION); final String action = intent.getAction(); final Set<Media> media = actionToMedia(lastAction == null ? action : lastAction); this.rtpConnectionReference = null; - Log.d(Config.LOGTAG, "attempting retry with " + with.toEscapedString()); + Log.d(Config.LOGTAG, "attempting retry with " + with.toString()); CallIntegrationConnectionService.placeCall(xmppConnectionService, account, with, media); } @@ -1446,7 +1441,7 @@ public class RtpSessionActivity extends XmppActivity private void recordVoiceMail(final View view) { final Intent intent = getIntent(); final Account account = extractAccount(intent); - final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH)); + final Jid with = Jid.of(intent.getStringExtra(EXTRA_WITH)); final Conversation conversation = xmppConnectionService.findOrCreateConversation(account, with, false, true); final Intent launchIntent = new Intent(this, ConversationsActivity.class); @@ -1611,7 +1606,7 @@ public class RtpSessionActivity extends XmppActivity return; } final Set<Media> media = actionToMedia(currentIntent.getStringExtra(EXTRA_LAST_ACTION)); - if (Jid.ofEscaped(withExtra).asBareJid().equals(with)) { + if (Jid.of(withExtra).asBareJid().equals(with)) { runOnUiThread( () -> { updateVerifiedShield(false); @@ -1634,11 +1629,11 @@ public class RtpSessionActivity extends XmppActivity private void resetIntent( final Account account, Jid with, final RtpEndUserState state, final Set<Media> media) { final Intent intent = new Intent(Intent.ACTION_VIEW); - intent.putExtra(EXTRA_ACCOUNT, account.getJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().toString()); if (RtpCapability.jmiSupport(account.getRoster().getContact(with))) { - intent.putExtra(EXTRA_WITH, with.asBareJid().toEscapedString()); + intent.putExtra(EXTRA_WITH, with.asBareJid().toString()); } else { - intent.putExtra(EXTRA_WITH, with.toEscapedString()); + intent.putExtra(EXTRA_WITH, with.toString()); } intent.putExtra(EXTRA_LAST_REPORTED_STATE, state.toString()); intent.putExtra( diff --git a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java index 1f83d1f45..a081f3755 100644 --- a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java @@ -216,7 +216,7 @@ public class ShareWithActivity extends XmppActivity final Conversation conversation; Account account; try { - account = xmppConnectionService.findAccountByJid(Jid.ofEscaped(share.account)); + account = xmppConnectionService.findAccountByJid(Jid.of(share.account)); } catch (final IllegalArgumentException e) { account = null; } diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index a8a0cb058..a2ec5fe94 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -36,7 +36,6 @@ import android.widget.EditText; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; - import androidx.annotation.MenuRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -52,7 +51,6 @@ import androidx.fragment.app.FragmentTransaction; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager.widget.ViewPager; - import com.google.android.material.color.MaterialColors; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.textfield.TextInputLayout; @@ -60,7 +58,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.leinardi.android.speeddial.SpeedDialActionItem; import com.leinardi.android.speeddial.SpeedDialView; - import eu.siacs.conversations.BuildConfig; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; @@ -87,7 +84,6 @@ import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.XmppConnection; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -359,7 +355,7 @@ public class StartConversationActivity extends XmppActivity mSearchEditText != null ? mSearchEditText.getText().toString() : null; final String prefilled; if (isValidJid(searchString)) { - prefilled = Jid.ofEscaped(searchString).toEscapedString(); + prefilled = Jid.of(searchString).toString(); } else { prefilled = null; } @@ -418,14 +414,15 @@ public class StartConversationActivity extends XmppActivity .create(); speedDialView.addActionItem(actionItem); } - speedDialView.setContentDescription(getString(R.string.add_contact_or_create_or_join_group_chat)); + speedDialView.setContentDescription( + getString(R.string.add_contact_or_create_or_join_group_chat)); } - public static boolean isValidJid(String input) { + public static boolean isValidJid(final String input) { try { - Jid jid = Jid.ofEscaped(input); + final Jid jid = Jid.ofUserInput(input); return !jid.isDomainJid(); - } catch (IllegalArgumentException e) { + } catch (final IllegalArgumentException e) { return false; } } @@ -505,7 +502,7 @@ public class StartConversationActivity extends XmppActivity protected void shareBookmarkUri(int position) { Bookmark bookmark = (Bookmark) conferences.get(position); - shareAsChannel(this, bookmark.getJid().asBareJid().toEscapedString()); + shareAsChannel(this, bookmark.getJid().asBareJid().toString()); } public static void shareAsChannel(final Context context, final String address) { @@ -549,7 +546,7 @@ public class StartConversationActivity extends XmppActivity protected void showQrForContact() { int position = contact_context_id; Contact contact = (Contact) contacts.get(position); - showQrCode("xmpp:" + contact.getJid().asBareJid().toEscapedString()); + showQrCode("xmpp:" + contact.getJid().asBareJid().toString()); } protected void toggleContactBlock() { @@ -564,8 +561,7 @@ public class StartConversationActivity extends XmppActivity builder.setNegativeButton(R.string.cancel, null); builder.setTitle(R.string.action_delete_contact); builder.setMessage( - JidDialog.style( - this, R.string.remove_contact_text, contact.getJid().toEscapedString())); + JidDialog.style(this, R.string.remove_contact_text, contact.getJid().toString())); builder.setPositiveButton( R.string.delete, (dialog, which) -> { @@ -588,11 +584,10 @@ public class StartConversationActivity extends XmppActivity JidDialog.style( this, R.string.remove_bookmark_and_close, - bookmark.getJid().toEscapedString())); + bookmark.getJid().toString())); } else { builder.setMessage( - JidDialog.style( - this, R.string.remove_bookmark, bookmark.getJid().toEscapedString())); + JidDialog.style(this, R.string.remove_bookmark, bookmark.getJid().toString())); } builder.setPositiveButton( hasConversation ? R.string.delete_and_close : R.string.delete, @@ -710,7 +705,7 @@ public class StartConversationActivity extends XmppActivity if (context instanceof XmppActivity) { final Jid jid; try { - jid = Jid.ofEscaped(spinner.getText().toString()); + jid = Jid.of(spinner.getText().toString()); } catch (final IllegalArgumentException e) { return null; } @@ -1078,11 +1073,11 @@ public class StartConversationActivity extends XmppActivity switchToConversationDoNotAppend(muc, invite.getBody()); return true; } else { - showJoinConferenceDialog(invite.getJid().asBareJid().toEscapedString()); + showJoinConferenceDialog(invite.getJid().asBareJid().toString()); return false; } - } else if (contacts.size() == 0) { - showCreateContactDialog(invite.getJid().toEscapedString(), invite); + } else if (contacts.isEmpty()) { + showCreateContactDialog(invite.getJid().toString(), invite); return false; } else if (contacts.size() == 1) { Contact contact = contacts.get(0); @@ -1106,10 +1101,10 @@ public class StartConversationActivity extends XmppActivity if (mMenuSearchView != null) { mMenuSearchView.expandActionView(); mSearchEditText.setText(""); - mSearchEditText.append(invite.getJid().toEscapedString()); - filter(invite.getJid().toEscapedString()); + mSearchEditText.append(invite.getJid().toString()); + filter(invite.getJid().toString()); } else { - mInitialSearchValue.push(invite.getJid().toEscapedString()); + mInitialSearchValue.push(invite.getJid().toString()); } return true; } @@ -1125,7 +1120,7 @@ public class StartConversationActivity extends XmppActivity JidDialog.style( this, R.string.verifying_omemo_keys_trusted_source, - contact.getJid().asBareJid().toEscapedString(), + contact.getJid().asBareJid().toString(), contact.getDisplayName())); builder.setView(view); builder.setPositiveButton( @@ -1237,8 +1232,7 @@ public class StartConversationActivity extends XmppActivity intent.putExtra(ChooseContactActivity.EXTRA_SELECT_MULTIPLE, true); intent.putExtra(ChooseContactActivity.EXTRA_GROUP_CHAT_NAME, name.trim()); intent.putExtra( - ChooseContactActivity.EXTRA_ACCOUNT, - account.getJid().asBareJid().toEscapedString()); + ChooseContactActivity.EXTRA_ACCOUNT, account.getJid().asBareJid().toString()); intent.putExtra(ChooseContactActivity.EXTRA_TITLE_RES_ID, R.string.choose_participants); startActivityForResult(intent, REQUEST_CREATE_CONFERENCE); } @@ -1259,13 +1253,13 @@ public class StartConversationActivity extends XmppActivity final String input = jid.getText().toString().trim(); Jid conferenceJid; try { - conferenceJid = Jid.ofEscaped(input); + conferenceJid = Jid.ofUserInput(input); } catch (final IllegalArgumentException e) { final XmppUri xmppUri = new XmppUri(input); if (xmppUri.isValidJid() && xmppUri.isAction(XmppUri.ACTION_JOIN)) { final Editable editable = jid.getEditableText(); editable.clear(); - editable.append(xmppUri.getJid().toEscapedString()); + editable.append(xmppUri.getJid().toString()); conferenceJid = xmppUri.getJid(); } else { layout.setError(getString(R.string.invalid_jid)); diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java index ba069b9a5..c1a5ef08c 100644 --- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java @@ -1,6 +1,5 @@ package eu.siacs.conversations.ui; -import android.app.AlertDialog; import android.content.Intent; import android.os.Bundle; import android.util.Log; @@ -10,12 +9,9 @@ import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.Toast; - import androidx.appcompat.app.ActionBar; import androidx.databinding.DataBindingUtil; - import com.google.android.material.dialog.MaterialAlertDialogBuilder; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.OmemoSetting; @@ -32,429 +28,515 @@ import eu.siacs.conversations.utils.IrregularUnicodeDetector; import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; - -import org.whispersystems.libsignal.IdentityKey; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import org.whispersystems.libsignal.IdentityKey; public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdated { - private final Map<String, Boolean> ownKeysToTrust = new HashMap<>(); - private final Map<Jid, Map<String, Boolean>> foreignKeysToTrust = new HashMap<>(); - private final OnClickListener mCancelButtonListener = v -> { - setResult(RESULT_CANCELED); - finish(); - }; - private List<Jid> contactJids; - private Account mAccount; - private Conversation mConversation; - private final OnClickListener mSaveButtonListener = v -> { - commitTrusts(); - finishOk(false); - }; - private final AtomicBoolean mUseCameraHintShown = new AtomicBoolean(false); - private AxolotlService.FetchStatus lastFetchReport = AxolotlService.FetchStatus.SUCCESS; - private Toast mUseCameraHintToast = null; - private ActivityTrustKeysBinding binding; - - @Override - protected void refreshUiReal() { - invalidateOptionsMenu(); - populateView(); - } - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - this.binding = DataBindingUtil.setContentView(this, R.layout.activity_trust_keys); - Activities.setStatusAndNavigationBarColors(this, binding.getRoot()); - this.contactJids = new ArrayList<>(); - final var intent = getIntent(); - final String[] contacts = intent == null ? null : intent.getStringArrayExtra("contacts"); - for (final String jid : (contacts == null ? new String[0] : contacts)) { - try { - this.contactJids.add(Jid.of(jid)); - } catch (final IllegalArgumentException ignored) { - } - } - - binding.cancelButton.setOnClickListener(mCancelButtonListener); - binding.saveButton.setOnClickListener(mSaveButtonListener); - - setSupportActionBar(binding.toolbar); - configureActionBar(getSupportActionBar()); - - if (savedInstanceState != null) { - mUseCameraHintShown.set(savedInstanceState.getBoolean("camera_hint_shown", false)); - } - } - - @Override - public void onSaveInstanceState(Bundle savedInstanceState) { - savedInstanceState.putBoolean("camera_hint_shown", mUseCameraHintShown.get()); - super.onSaveInstanceState(savedInstanceState); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.trust_keys, menu); - MenuItem scanQrCode = menu.findItem(R.id.action_scan_qr_code); - scanQrCode.setVisible((!ownKeysToTrust.isEmpty() || foreignActuallyHasKeys()) && isCameraFeatureAvailable()); - return super.onCreateOptionsMenu(menu); - } - - private void showCameraToast() { - mUseCameraHintToast = Toast.makeText(this, R.string.use_camera_icon_to_scan_barcode, Toast.LENGTH_LONG); - ActionBar actionBar = getSupportActionBar(); - mUseCameraHintToast.setGravity(Gravity.TOP | Gravity.END, 0, actionBar == null ? 0 : actionBar.getHeight()); - mUseCameraHintToast.show(); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.action_scan_qr_code: - if (hasPendingKeyFetches()) { - Toast.makeText(this, R.string.please_wait_for_keys_to_be_fetched, Toast.LENGTH_SHORT).show(); - } else { - ScanActivity.scan(this); - //new IntentIntegrator(this).initiateScan(Arrays.asList("AZTEC","QR_CODE")); - return true; - } - } - return super.onOptionsItemSelected(item); - } - - @Override - protected void onStop() { - super.onStop(); - if (mUseCameraHintToast != null) { - mUseCameraHintToast.cancel(); - } - } - - @Override - protected void processFingerprintVerification(XmppUri uri) { - if (mConversation != null - && mAccount != null - && uri.hasFingerprints() - && mAccount.getAxolotlService().getCryptoTargets(mConversation).contains(uri.getJid())) { - boolean performedVerification = xmppConnectionService.verifyFingerprints(mAccount.getRoster().getContact(uri.getJid()), uri.getFingerprints()); - boolean keys = reloadFingerprints(); - if (performedVerification && !keys && !hasNoOtherTrustedKeys() && !hasPendingKeyFetches()) { - Toast.makeText(this, R.string.all_omemo_keys_have_been_verified, Toast.LENGTH_SHORT).show(); - finishOk(false); - return; - } else if (performedVerification) { - Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT).show(); - } - } else { - reloadFingerprints(); - Log.d(Config.LOGTAG, "xmpp uri was: " + uri.getJid() + " has Fingerprints: " + uri.hasFingerprints()); - Toast.makeText(this, R.string.barcode_does_not_contain_fingerprints_for_this_chat, Toast.LENGTH_SHORT).show(); - } - populateView(); - } - - private void populateView() { - setTitle(getString(R.string.trust_omemo_fingerprints)); - binding.ownKeysDetails.removeAllViews(); - binding.foreignKeys.removeAllViews(); - boolean hasOwnKeys = false; - boolean hasForeignKeys = false; - for (final String fingerprint : ownKeysToTrust.keySet()) { - hasOwnKeys = true; - addFingerprintRowWithListeners(binding.ownKeysDetails, mAccount, fingerprint, false, - FingerprintStatus.createActive(ownKeysToTrust.get(fingerprint)), false, false, - (buttonView, isChecked) -> { - ownKeysToTrust.put(fingerprint, isChecked); - // own fingerprints have no impact on locked status. - } - ); - } - - synchronized (this.foreignKeysToTrust) { - for (Map.Entry<Jid, Map<String, Boolean>> entry : foreignKeysToTrust.entrySet()) { - hasForeignKeys = true; - KeysCardBinding keysCardBinding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.keys_card, binding.foreignKeys, false); - final Jid jid = entry.getKey(); - keysCardBinding.foreignKeysTitle.setText(IrregularUnicodeDetector.style(this, jid)); - keysCardBinding.foreignKeysTitle.setOnClickListener(v -> switchToContactDetails(mAccount.getRoster().getContact(jid))); - final Map<String, Boolean> fingerprints = entry.getValue(); - for (final String fingerprint : fingerprints.keySet()) { - addFingerprintRowWithListeners(keysCardBinding.foreignKeysDetails, mAccount, fingerprint, false, - FingerprintStatus.createActive(fingerprints.get(fingerprint)), false, false, - (buttonView, isChecked) -> { - fingerprints.put(fingerprint, isChecked); - lockOrUnlockAsNeeded(); - } - ); - } - if (fingerprints.isEmpty()) { - keysCardBinding.noKeysToAccept.setVisibility(View.VISIBLE); - if (hasNoOtherTrustedKeys(jid)) { - if (!mAccount.getRoster().getContact(jid).mutualPresenceSubscription()) { - keysCardBinding.noKeysToAccept.setText(R.string.error_no_keys_to_trust_presence); - } else { - keysCardBinding.noKeysToAccept.setText(R.string.error_no_keys_to_trust_server_error); - } - } else { - keysCardBinding.noKeysToAccept.setText(getString(R.string.no_keys_just_confirm, mAccount.getRoster().getContact(jid).getDisplayName())); - } - } else { - keysCardBinding.noKeysToAccept.setVisibility(View.GONE); - } - binding.foreignKeys.addView(keysCardBinding.foreignKeysCard); - } - } - - if ((hasOwnKeys || foreignActuallyHasKeys()) && isCameraFeatureAvailable() && mUseCameraHintShown.compareAndSet(false, true)) { - showCameraToast(); - } - - binding.ownKeysTitle.setText(mAccount.getJid().asBareJid().toEscapedString()); - binding.ownKeysCard.setVisibility(hasOwnKeys ? View.VISIBLE : View.GONE); - binding.foreignKeys.setVisibility(hasForeignKeys ? View.VISIBLE : View.GONE); - if (hasPendingKeyFetches()) { - setFetching(); - lock(); - } else { - if (!hasForeignKeys && hasNoOtherTrustedKeys()) { - binding.keyErrorMessageCard.setVisibility(View.VISIBLE); - boolean lastReportWasError = lastFetchReport == AxolotlService.FetchStatus.ERROR; - boolean errorFetchingBundle = mAccount.getAxolotlService().fetchMapHasErrors(contactJids); - boolean errorFetchingDeviceList = mAccount.getAxolotlService().hasErrorFetchingDeviceList(contactJids); - boolean anyWithoutMutualPresenceSubscription = anyWithoutMutualPresenceSubscription(contactJids); - if (errorFetchingDeviceList) { - binding.keyErrorMessage.setVisibility(View.VISIBLE); - binding.keyErrorMessage.setText(R.string.error_trustkey_device_list); - } else if (errorFetchingBundle || lastReportWasError) { - binding.keyErrorMessage.setVisibility(View.VISIBLE); - binding.keyErrorMessage.setText(R.string.error_trustkey_bundle); - } else { - binding.keyErrorMessage.setVisibility(View.GONE); - } - this.binding.keyErrorHintMutual.setVisibility(anyWithoutMutualPresenceSubscription ? View.VISIBLE : View.GONE); - Contact contact = mAccount.getRoster().getContact(contactJids.get(0)); - binding.keyErrorGeneral.setText(getString(R.string.error_trustkey_general, getString(R.string.app_name), contact.getDisplayName())); - binding.ownKeysDetails.removeAllViews(); - if (OmemoSetting.isAlways()) { - binding.disableButton.setVisibility(View.GONE); - } else { - binding.disableButton.setVisibility(View.VISIBLE); - binding.disableButton.setOnClickListener(this::disableEncryptionDialog); - } - binding.ownKeysCard.setVisibility(View.GONE); - binding.foreignKeys.removeAllViews(); - binding.foreignKeys.setVisibility(View.GONE); - } - lockOrUnlockAsNeeded(); - setDone(); - } - } - - private void disableEncryptionDialog(final View view) { - final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); - builder.setTitle(R.string.disable_encryption); - builder.setMessage(R.string.disable_encryption_message); - builder.setPositiveButton(R.string.disable_now, (dialog, which) -> { - mConversation.setNextEncryption(Message.ENCRYPTION_NONE); - xmppConnectionService.updateConversation(mConversation); - finishOk(true); - }); - builder.setNegativeButton(R.string.cancel, null); - builder.create().show(); - } - - private boolean anyWithoutMutualPresenceSubscription(List<Jid> contactJids) { - for (Jid jid : contactJids) { - if (!mAccount.getRoster().getContact(jid).mutualPresenceSubscription()) { - return true; - } - } - return false; - } - - private boolean foreignActuallyHasKeys() { - synchronized (this.foreignKeysToTrust) { - for (Map.Entry<Jid, Map<String, Boolean>> entry : foreignKeysToTrust.entrySet()) { - if (!entry.getValue().isEmpty()) { - return true; - } - } - } - return false; - } - - private boolean reloadFingerprints() { - List<Jid> acceptedTargets = mConversation == null ? new ArrayList<>() : mConversation.getAcceptedCryptoTargets(); - ownKeysToTrust.clear(); - if (this.mAccount == null) { - return false; - } - AxolotlService service = this.mAccount.getAxolotlService(); - Set<IdentityKey> ownKeysSet = service.getKeysWithTrust(FingerprintStatus.createActiveUndecided()); - for (final IdentityKey identityKey : ownKeysSet) { - final String fingerprint = CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize()); - if (!ownKeysToTrust.containsKey(fingerprint)) { - ownKeysToTrust.put(fingerprint, false); - } - } - synchronized (this.foreignKeysToTrust) { - foreignKeysToTrust.clear(); - for (Jid jid : contactJids) { - Set<IdentityKey> foreignKeysSet = service.getKeysWithTrust(FingerprintStatus.createActiveUndecided(), jid); - if (hasNoOtherTrustedKeys(jid) && ownKeysSet.isEmpty()) { - foreignKeysSet.addAll(service.getKeysWithTrust(FingerprintStatus.createActive(false), jid)); - } - Map<String, Boolean> foreignFingerprints = new HashMap<>(); - for (final IdentityKey identityKey : foreignKeysSet) { - final String fingerprint = CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize()); - if (!foreignFingerprints.containsKey(fingerprint)) { - foreignFingerprints.put(fingerprint, false); - } - } - if (!foreignFingerprints.isEmpty() || !acceptedTargets.contains(jid)) { - foreignKeysToTrust.put(jid, foreignFingerprints); - } - } - } - return ownKeysSet.size() + foreignKeysToTrust.size() > 0; - } - - public void onBackendConnected() { - Intent intent = getIntent(); - this.mAccount = extractAccount(intent); - if (this.mAccount != null && intent != null) { - String uuid = intent.getStringExtra("conversation"); - this.mConversation = xmppConnectionService.findConversationByUuid(uuid); - if (this.mPendingFingerprintVerificationUri != null) { - processFingerprintVerification(this.mPendingFingerprintVerificationUri); - this.mPendingFingerprintVerificationUri = null; - } else { - final boolean keysToTrust = reloadFingerprints(); - if (keysToTrust || hasPendingKeyFetches() || hasNoOtherTrustedKeys()) { - populateView(); - invalidateOptionsMenu(); - } else { - finishOk(false); - } - } - } - } - - private boolean hasNoOtherTrustedKeys() { - return mAccount == null || mAccount.getAxolotlService().anyTargetHasNoTrustedKeys(contactJids); - } - - private boolean hasNoOtherTrustedKeys(Jid contact) { - return mAccount == null || mAccount.getAxolotlService().getNumTrustedKeys(contact) == 0; - } - - private boolean hasPendingKeyFetches() { - return mAccount != null && mAccount.getAxolotlService().hasPendingKeyFetches(contactJids); - } - - - @Override - public void onKeyStatusUpdated(final AxolotlService.FetchStatus report) { - final boolean keysToTrust = reloadFingerprints(); - if (report != null) { - lastFetchReport = report; - runOnUiThread(() -> { - if (mUseCameraHintToast != null && !keysToTrust) { - mUseCameraHintToast.cancel(); - } - switch (report) { - case ERROR: - Toast.makeText(TrustKeysActivity.this, R.string.error_fetching_omemo_key, Toast.LENGTH_SHORT).show(); - break; - case SUCCESS_TRUSTED: - Toast.makeText(TrustKeysActivity.this, R.string.blindly_trusted_omemo_keys, Toast.LENGTH_LONG).show(); - break; - case SUCCESS_VERIFIED: - Toast.makeText(TrustKeysActivity.this, - Config.X509_VERIFICATION ? R.string.verified_omemo_key_with_certificate : R.string.all_omemo_keys_have_been_verified, - Toast.LENGTH_LONG).show(); - break; - } - }); - - } - if (keysToTrust || hasPendingKeyFetches() || hasNoOtherTrustedKeys()) { - refreshUi(); - } else { - runOnUiThread(() -> finishOk(false)); - - } - } - - private void finishOk(boolean disabled) { - Intent data = new Intent(); - data.putExtra("choice", getIntent().getIntExtra("choice", ConversationFragment.ATTACHMENT_CHOICE_INVALID)); - data.putExtra("disabled", disabled); - setResult(RESULT_OK, data); - finish(); - } - - private void commitTrusts() { - for (final String fingerprint : ownKeysToTrust.keySet()) { - mAccount.getAxolotlService().setFingerprintTrust( - fingerprint, - FingerprintStatus.createActive(ownKeysToTrust.get(fingerprint))); - } - List<Jid> acceptedTargets = mConversation == null ? new ArrayList<>() : mConversation.getAcceptedCryptoTargets(); - synchronized (this.foreignKeysToTrust) { - for (Map.Entry<Jid, Map<String, Boolean>> entry : foreignKeysToTrust.entrySet()) { - Jid jid = entry.getKey(); - Map<String, Boolean> value = entry.getValue(); - if (!acceptedTargets.contains(jid)) { - acceptedTargets.add(jid); - } - for (final String fingerprint : value.keySet()) { - mAccount.getAxolotlService().setFingerprintTrust( - fingerprint, - FingerprintStatus.createActive(value.get(fingerprint))); - } - } - } - if (mConversation != null && mConversation.getMode() == Conversation.MODE_MULTI) { - mConversation.setAcceptedCryptoTargets(acceptedTargets); - xmppConnectionService.updateConversation(mConversation); - } - } - - private void unlock() { - binding.saveButton.setEnabled(true); - } - - private void lock() { - binding.saveButton.setEnabled(false); - } - - private void lockOrUnlockAsNeeded() { - synchronized (this.foreignKeysToTrust) { - for (Jid jid : contactJids) { - Map<String, Boolean> fingerprints = foreignKeysToTrust.get(jid); - if (hasNoOtherTrustedKeys(jid) && (fingerprints == null || !fingerprints.containsValue(true))) { - lock(); - return; - } - } - } - unlock(); - - } - - private void setDone() { - binding.saveButton.setText(getString(R.string.done)); - } - - private void setFetching() { - binding.saveButton.setText(getString(R.string.fetching_keys)); - } + private final Map<String, Boolean> ownKeysToTrust = new HashMap<>(); + private final Map<Jid, Map<String, Boolean>> foreignKeysToTrust = new HashMap<>(); + private final OnClickListener mCancelButtonListener = + v -> { + setResult(RESULT_CANCELED); + finish(); + }; + private List<Jid> contactJids; + private Account mAccount; + private Conversation mConversation; + private final OnClickListener mSaveButtonListener = + v -> { + commitTrusts(); + finishOk(false); + }; + private final AtomicBoolean mUseCameraHintShown = new AtomicBoolean(false); + private AxolotlService.FetchStatus lastFetchReport = AxolotlService.FetchStatus.SUCCESS; + private Toast mUseCameraHintToast = null; + private ActivityTrustKeysBinding binding; + + @Override + protected void refreshUiReal() { + invalidateOptionsMenu(); + populateView(); + } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + this.binding = DataBindingUtil.setContentView(this, R.layout.activity_trust_keys); + Activities.setStatusAndNavigationBarColors(this, binding.getRoot()); + this.contactJids = new ArrayList<>(); + final var intent = getIntent(); + final String[] contacts = intent == null ? null : intent.getStringArrayExtra("contacts"); + for (final String jid : (contacts == null ? new String[0] : contacts)) { + try { + this.contactJids.add(Jid.of(jid)); + } catch (final IllegalArgumentException ignored) { + } + } + + binding.cancelButton.setOnClickListener(mCancelButtonListener); + binding.saveButton.setOnClickListener(mSaveButtonListener); + + setSupportActionBar(binding.toolbar); + configureActionBar(getSupportActionBar()); + + if (savedInstanceState != null) { + mUseCameraHintShown.set(savedInstanceState.getBoolean("camera_hint_shown", false)); + } + } + + @Override + public void onSaveInstanceState(Bundle savedInstanceState) { + savedInstanceState.putBoolean("camera_hint_shown", mUseCameraHintShown.get()); + super.onSaveInstanceState(savedInstanceState); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.trust_keys, menu); + MenuItem scanQrCode = menu.findItem(R.id.action_scan_qr_code); + scanQrCode.setVisible( + (!ownKeysToTrust.isEmpty() || foreignActuallyHasKeys()) + && isCameraFeatureAvailable()); + return super.onCreateOptionsMenu(menu); + } + + private void showCameraToast() { + mUseCameraHintToast = + Toast.makeText(this, R.string.use_camera_icon_to_scan_barcode, Toast.LENGTH_LONG); + ActionBar actionBar = getSupportActionBar(); + mUseCameraHintToast.setGravity( + Gravity.TOP | Gravity.END, 0, actionBar == null ? 0 : actionBar.getHeight()); + mUseCameraHintToast.show(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_scan_qr_code: + if (hasPendingKeyFetches()) { + Toast.makeText( + this, + R.string.please_wait_for_keys_to_be_fetched, + Toast.LENGTH_SHORT) + .show(); + } else { + ScanActivity.scan(this); + // new IntentIntegrator(this).initiateScan(Arrays.asList("AZTEC","QR_CODE")); + return true; + } + } + return super.onOptionsItemSelected(item); + } + + @Override + protected void onStop() { + super.onStop(); + if (mUseCameraHintToast != null) { + mUseCameraHintToast.cancel(); + } + } + + @Override + protected void processFingerprintVerification(XmppUri uri) { + if (mConversation != null + && mAccount != null + && uri.hasFingerprints() + && mAccount.getAxolotlService() + .getCryptoTargets(mConversation) + .contains(uri.getJid())) { + boolean performedVerification = + xmppConnectionService.verifyFingerprints( + mAccount.getRoster().getContact(uri.getJid()), uri.getFingerprints()); + boolean keys = reloadFingerprints(); + if (performedVerification + && !keys + && !hasNoOtherTrustedKeys() + && !hasPendingKeyFetches()) { + Toast.makeText(this, R.string.all_omemo_keys_have_been_verified, Toast.LENGTH_SHORT) + .show(); + finishOk(false); + return; + } else if (performedVerification) { + Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT).show(); + } + } else { + reloadFingerprints(); + Log.d( + Config.LOGTAG, + "xmpp uri was: " + + uri.getJid() + + " has Fingerprints: " + + uri.hasFingerprints()); + Toast.makeText( + this, + R.string.barcode_does_not_contain_fingerprints_for_this_chat, + Toast.LENGTH_SHORT) + .show(); + } + populateView(); + } + + private void populateView() { + setTitle(getString(R.string.trust_omemo_fingerprints)); + binding.ownKeysDetails.removeAllViews(); + binding.foreignKeys.removeAllViews(); + boolean hasOwnKeys = false; + boolean hasForeignKeys = false; + for (final String fingerprint : ownKeysToTrust.keySet()) { + hasOwnKeys = true; + addFingerprintRowWithListeners( + binding.ownKeysDetails, + mAccount, + fingerprint, + false, + FingerprintStatus.createActive(ownKeysToTrust.get(fingerprint)), + false, + false, + (buttonView, isChecked) -> { + ownKeysToTrust.put(fingerprint, isChecked); + // own fingerprints have no impact on locked status. + }); + } + + synchronized (this.foreignKeysToTrust) { + for (Map.Entry<Jid, Map<String, Boolean>> entry : foreignKeysToTrust.entrySet()) { + hasForeignKeys = true; + KeysCardBinding keysCardBinding = + DataBindingUtil.inflate( + getLayoutInflater(), + R.layout.keys_card, + binding.foreignKeys, + false); + final Jid jid = entry.getKey(); + keysCardBinding.foreignKeysTitle.setText(IrregularUnicodeDetector.style(this, jid)); + keysCardBinding.foreignKeysTitle.setOnClickListener( + v -> switchToContactDetails(mAccount.getRoster().getContact(jid))); + final Map<String, Boolean> fingerprints = entry.getValue(); + for (final String fingerprint : fingerprints.keySet()) { + addFingerprintRowWithListeners( + keysCardBinding.foreignKeysDetails, + mAccount, + fingerprint, + false, + FingerprintStatus.createActive(fingerprints.get(fingerprint)), + false, + false, + (buttonView, isChecked) -> { + fingerprints.put(fingerprint, isChecked); + lockOrUnlockAsNeeded(); + }); + } + if (fingerprints.isEmpty()) { + keysCardBinding.noKeysToAccept.setVisibility(View.VISIBLE); + if (hasNoOtherTrustedKeys(jid)) { + if (!mAccount.getRoster().getContact(jid).mutualPresenceSubscription()) { + keysCardBinding.noKeysToAccept.setText( + R.string.error_no_keys_to_trust_presence); + } else { + keysCardBinding.noKeysToAccept.setText( + R.string.error_no_keys_to_trust_server_error); + } + } else { + keysCardBinding.noKeysToAccept.setText( + getString( + R.string.no_keys_just_confirm, + mAccount.getRoster().getContact(jid).getDisplayName())); + } + } else { + keysCardBinding.noKeysToAccept.setVisibility(View.GONE); + } + binding.foreignKeys.addView(keysCardBinding.foreignKeysCard); + } + } + + if ((hasOwnKeys || foreignActuallyHasKeys()) + && isCameraFeatureAvailable() + && mUseCameraHintShown.compareAndSet(false, true)) { + showCameraToast(); + } + + binding.ownKeysTitle.setText(mAccount.getJid().asBareJid().toString()); + binding.ownKeysCard.setVisibility(hasOwnKeys ? View.VISIBLE : View.GONE); + binding.foreignKeys.setVisibility(hasForeignKeys ? View.VISIBLE : View.GONE); + if (hasPendingKeyFetches()) { + setFetching(); + lock(); + } else { + if (!hasForeignKeys && hasNoOtherTrustedKeys()) { + binding.keyErrorMessageCard.setVisibility(View.VISIBLE); + boolean lastReportWasError = lastFetchReport == AxolotlService.FetchStatus.ERROR; + boolean errorFetchingBundle = + mAccount.getAxolotlService().fetchMapHasErrors(contactJids); + boolean errorFetchingDeviceList = + mAccount.getAxolotlService().hasErrorFetchingDeviceList(contactJids); + boolean anyWithoutMutualPresenceSubscription = + anyWithoutMutualPresenceSubscription(contactJids); + if (errorFetchingDeviceList) { + binding.keyErrorMessage.setVisibility(View.VISIBLE); + binding.keyErrorMessage.setText(R.string.error_trustkey_device_list); + } else if (errorFetchingBundle || lastReportWasError) { + binding.keyErrorMessage.setVisibility(View.VISIBLE); + binding.keyErrorMessage.setText(R.string.error_trustkey_bundle); + } else { + binding.keyErrorMessage.setVisibility(View.GONE); + } + this.binding.keyErrorHintMutual.setVisibility( + anyWithoutMutualPresenceSubscription ? View.VISIBLE : View.GONE); + Contact contact = mAccount.getRoster().getContact(contactJids.get(0)); + binding.keyErrorGeneral.setText( + getString( + R.string.error_trustkey_general, + getString(R.string.app_name), + contact.getDisplayName())); + binding.ownKeysDetails.removeAllViews(); + if (OmemoSetting.isAlways()) { + binding.disableButton.setVisibility(View.GONE); + } else { + binding.disableButton.setVisibility(View.VISIBLE); + binding.disableButton.setOnClickListener(this::disableEncryptionDialog); + } + binding.ownKeysCard.setVisibility(View.GONE); + binding.foreignKeys.removeAllViews(); + binding.foreignKeys.setVisibility(View.GONE); + } + lockOrUnlockAsNeeded(); + setDone(); + } + } + + private void disableEncryptionDialog(final View view) { + final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); + builder.setTitle(R.string.disable_encryption); + builder.setMessage(R.string.disable_encryption_message); + builder.setPositiveButton( + R.string.disable_now, + (dialog, which) -> { + mConversation.setNextEncryption(Message.ENCRYPTION_NONE); + xmppConnectionService.updateConversation(mConversation); + finishOk(true); + }); + builder.setNegativeButton(R.string.cancel, null); + builder.create().show(); + } + + private boolean anyWithoutMutualPresenceSubscription(List<Jid> contactJids) { + for (Jid jid : contactJids) { + if (!mAccount.getRoster().getContact(jid).mutualPresenceSubscription()) { + return true; + } + } + return false; + } + + private boolean foreignActuallyHasKeys() { + synchronized (this.foreignKeysToTrust) { + for (Map.Entry<Jid, Map<String, Boolean>> entry : foreignKeysToTrust.entrySet()) { + if (!entry.getValue().isEmpty()) { + return true; + } + } + } + return false; + } + + private boolean reloadFingerprints() { + List<Jid> acceptedTargets = + mConversation == null + ? new ArrayList<>() + : mConversation.getAcceptedCryptoTargets(); + ownKeysToTrust.clear(); + if (this.mAccount == null) { + return false; + } + AxolotlService service = this.mAccount.getAxolotlService(); + Set<IdentityKey> ownKeysSet = + service.getKeysWithTrust(FingerprintStatus.createActiveUndecided()); + for (final IdentityKey identityKey : ownKeysSet) { + final String fingerprint = + CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize()); + if (!ownKeysToTrust.containsKey(fingerprint)) { + ownKeysToTrust.put(fingerprint, false); + } + } + synchronized (this.foreignKeysToTrust) { + foreignKeysToTrust.clear(); + for (Jid jid : contactJids) { + Set<IdentityKey> foreignKeysSet = + service.getKeysWithTrust(FingerprintStatus.createActiveUndecided(), jid); + if (hasNoOtherTrustedKeys(jid) && ownKeysSet.isEmpty()) { + foreignKeysSet.addAll( + service.getKeysWithTrust(FingerprintStatus.createActive(false), jid)); + } + Map<String, Boolean> foreignFingerprints = new HashMap<>(); + for (final IdentityKey identityKey : foreignKeysSet) { + final String fingerprint = + CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize()); + if (!foreignFingerprints.containsKey(fingerprint)) { + foreignFingerprints.put(fingerprint, false); + } + } + if (!foreignFingerprints.isEmpty() || !acceptedTargets.contains(jid)) { + foreignKeysToTrust.put(jid, foreignFingerprints); + } + } + } + return ownKeysSet.size() + foreignKeysToTrust.size() > 0; + } + + public void onBackendConnected() { + Intent intent = getIntent(); + this.mAccount = extractAccount(intent); + if (this.mAccount != null && intent != null) { + String uuid = intent.getStringExtra("conversation"); + this.mConversation = xmppConnectionService.findConversationByUuid(uuid); + if (this.mPendingFingerprintVerificationUri != null) { + processFingerprintVerification(this.mPendingFingerprintVerificationUri); + this.mPendingFingerprintVerificationUri = null; + } else { + final boolean keysToTrust = reloadFingerprints(); + if (keysToTrust || hasPendingKeyFetches() || hasNoOtherTrustedKeys()) { + populateView(); + invalidateOptionsMenu(); + } else { + finishOk(false); + } + } + } + } + + private boolean hasNoOtherTrustedKeys() { + return mAccount == null + || mAccount.getAxolotlService().anyTargetHasNoTrustedKeys(contactJids); + } + + private boolean hasNoOtherTrustedKeys(Jid contact) { + return mAccount == null || mAccount.getAxolotlService().getNumTrustedKeys(contact) == 0; + } + + private boolean hasPendingKeyFetches() { + return mAccount != null && mAccount.getAxolotlService().hasPendingKeyFetches(contactJids); + } + + @Override + public void onKeyStatusUpdated(final AxolotlService.FetchStatus report) { + final boolean keysToTrust = reloadFingerprints(); + if (report != null) { + lastFetchReport = report; + runOnUiThread( + () -> { + if (mUseCameraHintToast != null && !keysToTrust) { + mUseCameraHintToast.cancel(); + } + switch (report) { + case ERROR: + Toast.makeText( + TrustKeysActivity.this, + R.string.error_fetching_omemo_key, + Toast.LENGTH_SHORT) + .show(); + break; + case SUCCESS_TRUSTED: + Toast.makeText( + TrustKeysActivity.this, + R.string.blindly_trusted_omemo_keys, + Toast.LENGTH_LONG) + .show(); + break; + case SUCCESS_VERIFIED: + Toast.makeText( + TrustKeysActivity.this, + Config.X509_VERIFICATION + ? R.string + .verified_omemo_key_with_certificate + : R.string + .all_omemo_keys_have_been_verified, + Toast.LENGTH_LONG) + .show(); + break; + } + }); + } + if (keysToTrust || hasPendingKeyFetches() || hasNoOtherTrustedKeys()) { + refreshUi(); + } else { + runOnUiThread(() -> finishOk(false)); + } + } + + private void finishOk(boolean disabled) { + Intent data = new Intent(); + data.putExtra( + "choice", + getIntent().getIntExtra("choice", ConversationFragment.ATTACHMENT_CHOICE_INVALID)); + data.putExtra("disabled", disabled); + setResult(RESULT_OK, data); + finish(); + } + + private void commitTrusts() { + for (final String fingerprint : ownKeysToTrust.keySet()) { + mAccount.getAxolotlService() + .setFingerprintTrust( + fingerprint, + FingerprintStatus.createActive(ownKeysToTrust.get(fingerprint))); + } + List<Jid> acceptedTargets = + mConversation == null + ? new ArrayList<>() + : mConversation.getAcceptedCryptoTargets(); + synchronized (this.foreignKeysToTrust) { + for (Map.Entry<Jid, Map<String, Boolean>> entry : foreignKeysToTrust.entrySet()) { + Jid jid = entry.getKey(); + Map<String, Boolean> value = entry.getValue(); + if (!acceptedTargets.contains(jid)) { + acceptedTargets.add(jid); + } + for (final String fingerprint : value.keySet()) { + mAccount.getAxolotlService() + .setFingerprintTrust( + fingerprint, + FingerprintStatus.createActive(value.get(fingerprint))); + } + } + } + if (mConversation != null && mConversation.getMode() == Conversation.MODE_MULTI) { + mConversation.setAcceptedCryptoTargets(acceptedTargets); + xmppConnectionService.updateConversation(mConversation); + } + } + + private void unlock() { + binding.saveButton.setEnabled(true); + } + + private void lock() { + binding.saveButton.setEnabled(false); + } + + private void lockOrUnlockAsNeeded() { + synchronized (this.foreignKeysToTrust) { + for (Jid jid : contactJids) { + Map<String, Boolean> fingerprints = foreignKeysToTrust.get(jid); + if (hasNoOtherTrustedKeys(jid) + && (fingerprints == null || !fingerprints.containsValue(true))) { + lock(); + return; + } + } + } + unlock(); + } + + private void setDone() { + binding.saveButton.setText(getString(R.string.done)); + } + + private void setFetching() { + binding.saveButton.setText(getString(R.string.fetching_keys)); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java b/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java index 55bf8b3ad..d4c63412b 100644 --- a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java @@ -9,14 +9,11 @@ import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.core.content.ContextCompat; import androidx.databinding.DataBindingUtil; - import com.google.common.base.Strings; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityUriHandlerBinding; @@ -27,18 +24,16 @@ import eu.siacs.conversations.utils.ProvisioningUtils; import eu.siacs.conversations.utils.SignupUtils; import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.Jid; - +import java.io.IOException; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import okhttp3.Call; import okhttp3.Callback; import okhttp3.HttpUrl; import okhttp3.Request; import okhttp3.Response; -import java.io.IOException; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - public class UriHandlerActivity extends BaseActivity { public static final String ACTION_SCAN_QR_CODE = "scan_qr_code"; @@ -56,7 +51,8 @@ public class UriHandlerActivity extends BaseActivity { } public static void scan(final Activity activity, final boolean provisioning) { - if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { + if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) + == PackageManager.PERMISSION_GRANTED) { final Intent intent = new Intent(activity, UriHandlerActivity.class); intent.setAction(UriHandlerActivity.ACTION_SCAN_QR_CODE); if (provisioning) { @@ -128,7 +124,7 @@ public class UriHandlerActivity extends BaseActivity { final String preAuth = xmppUri.getParameter(XmppUri.PARAMETER_PRE_AUTH); final Jid jid = xmppUri.getJid(); if (xmppUri.isAction(XmppUri.ACTION_REGISTER)) { - if (jid.getEscapedLocal() != null && accounts.contains(jid.asBareJid())) { + if (jid.getLocal() != null && accounts.contains(jid.asBareJid())) { showError(R.string.account_already_exists); return false; } @@ -172,13 +168,13 @@ public class UriHandlerActivity extends BaseActivity { final Class<?> clazz = findShareViaAccountClass(); if (clazz != null) { intent = new Intent(this, clazz); - intent.putExtra("contact", jid.toEscapedString()); + intent.putExtra("contact", jid.toString()); intent.putExtra("body", body); } else { intent = new Intent(this, StartConversationActivity.class); intent.setAction(Intent.ACTION_VIEW); intent.setData(uri); - intent.putExtra("account", accounts.get(0).toEscapedString()); + intent.putExtra("account", accounts.get(0).toString()); } } else { intent = new Intent(this, ShareWithActivity.class); @@ -209,8 +205,8 @@ public class UriHandlerActivity extends BaseActivity { private void checkForLinkHeader(final HttpUrl url) { Log.d(Config.LOGTAG, "checking for link header on " + url); this.call = - HttpConnectionManager.okHttpClient(this).newCall( - new Request.Builder().url(url).head().build()); + HttpConnectionManager.okHttpClient(this) + .newCall(new Request.Builder().url(url).head().build()); this.call.enqueue( new Callback() { @Override @@ -252,7 +248,7 @@ public class UriHandlerActivity extends BaseActivity { } private void showErrorOnUiThread(@StringRes int error) { - runOnUiThread(()-> showError(error)); + runOnUiThread(() -> showError(error)); } private static Class<?> findShareViaAccountClass() { @@ -350,4 +346,4 @@ public class UriHandlerActivity extends BaseActivity { final String trimmed = Strings.nullToEmpty(input).trim(); return trimmed.charAt(0) == '{' && trimmed.charAt(trimmed.length() - 1) == '}'; } -} \ No newline at end of file +} diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index fa304e810..8a3fc8a54 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -733,8 +733,8 @@ public abstract class XmppActivity extends ActionBarActivity { public void switchToContactDetails(Contact contact, String messageFingerprint) { Intent intent = new Intent(this, ContactDetailsActivity.class); intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT); - intent.putExtra(EXTRA_ACCOUNT, contact.getAccount().getJid().asBareJid().toEscapedString()); - intent.putExtra("contact", contact.getJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, contact.getAccount().getJid().asBareJid().toString()); + intent.putExtra("contact", contact.getJid().toString()); intent.putExtra("fingerprint", messageFingerprint); startActivity(intent); } @@ -749,7 +749,7 @@ public abstract class XmppActivity extends ActionBarActivity { public void switchToAccount(Account account, boolean init, String fingerprint) { Intent intent = new Intent(this, EditAccountActivity.class); - intent.putExtra("jid", account.getJid().asBareJid().toEscapedString()); + intent.putExtra("jid", account.getJid().asBareJid().toString()); intent.putExtra("init", init); if (init) { intent.setFlags( @@ -1141,7 +1141,7 @@ public abstract class XmppActivity extends ActionBarActivity { protected Account extractAccount(Intent intent) { final String jid = intent != null ? intent.getStringExtra(EXTRA_ACCOUNT) : null; try { - return jid != null ? xmppConnectionService.findAccountByJid(Jid.ofEscaped(jid)) : null; + return jid != null ? xmppConnectionService.findAccountByJid(Jid.of(jid)) : null; } catch (IllegalArgumentException e) { return null; } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java index 88475c580..b52280181 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java @@ -4,19 +4,14 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; - import androidx.annotation.NonNull; import androidx.databinding.DataBindingUtil; - import com.google.android.material.color.MaterialColors; - -import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ItemAccountBinding; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.ui.XmppActivity; import eu.siacs.conversations.ui.util.AvatarWorkerTask; - import java.util.List; public class AccountAdapter extends ArrayAdapter<Account> { @@ -42,27 +37,42 @@ public class AccountAdapter extends ArrayAdapter<Account> { final Account account = getItem(position); final ViewHolder viewHolder; if (view == null) { - ItemAccountBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.item_account, parent, false); + ItemAccountBinding binding = + DataBindingUtil.inflate( + LayoutInflater.from(parent.getContext()), + R.layout.item_account, + parent, + false); view = binding.getRoot(); viewHolder = new ViewHolder(binding); view.setTag(viewHolder); } else { viewHolder = (ViewHolder) view.getTag(); } - viewHolder.binding.accountJid.setText(account.getJid().asBareJid().toEscapedString()); + viewHolder.binding.accountJid.setText(account.getJid().asBareJid().toString()); AvatarWorkerTask.loadAvatar(account, viewHolder.binding.accountImage, R.dimen.avatar); - viewHolder.binding.accountStatus.setText(getContext().getString(account.getStatus().getReadableId())); + viewHolder.binding.accountStatus.setText( + getContext().getString(account.getStatus().getReadableId())); switch (account.getStatus()) { case ONLINE: - viewHolder.binding.accountStatus.setTextColor(MaterialColors.getColor(viewHolder.binding.accountStatus, com.google.android.material.R.attr.colorPrimary)); + viewHolder.binding.accountStatus.setTextColor( + MaterialColors.getColor( + viewHolder.binding.accountStatus, + com.google.android.material.R.attr.colorPrimary)); break; case DISABLED: case LOGGED_OUT: case CONNECTING: - viewHolder.binding.accountStatus.setTextColor(MaterialColors.getColor(viewHolder.binding.accountStatus, com.google.android.material.R.attr.colorOnSurfaceVariant)); + viewHolder.binding.accountStatus.setTextColor( + MaterialColors.getColor( + viewHolder.binding.accountStatus, + com.google.android.material.R.attr.colorOnSurfaceVariant)); break; default: - viewHolder.binding.accountStatus.setTextColor(MaterialColors.getColor(viewHolder.binding.accountStatus, com.google.android.material.R.attr.colorError)); + viewHolder.binding.accountStatus.setTextColor( + MaterialColors.getColor( + viewHolder.binding.accountStatus, + com.google.android.material.R.attr.colorError)); break; } final boolean isDisabled = (account.getStatus() == Account.State.DISABLED); @@ -73,11 +83,12 @@ public class AccountAdapter extends ArrayAdapter<Account> { } else { viewHolder.binding.tglAccountStatus.setVisibility(View.GONE); } - viewHolder.binding.tglAccountStatus.setOnCheckedChangeListener((compoundButton, b) -> { - if (b == isDisabled && activity instanceof OnTglAccountState) { - ((OnTglAccountState) activity).onClickTglAccountState(account, b); - } - }); + viewHolder.binding.tglAccountStatus.setOnCheckedChangeListener( + (compoundButton, b) -> { + if (b == isDisabled && activity instanceof OnTglAccountState) { + ((OnTglAccountState) activity).onClickTglAccountState(account, b); + } + }); return view; } @@ -89,10 +100,7 @@ public class AccountAdapter extends ArrayAdapter<Account> { } } - - public interface OnTglAccountState { void onClickTglAccountState(Account account, boolean state); } - } 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 b085cb964..88f7636bf 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java @@ -3,14 +3,10 @@ package eu.siacs.conversations.ui.adapter; import android.content.Context; import android.widget.ArrayAdapter; import android.widget.Filter; - import androidx.annotation.NonNull; - import com.google.common.collect.ImmutableList; import com.google.common.collect.Ordering; - import eu.siacs.conversations.Config; - import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -22,61 +18,66 @@ public class KnownHostsAdapter extends ArrayAdapter<String> { private static final Pattern E164_PATTERN = Pattern.compile("^\\+[1-9]\\d{1,14}$"); private List<String> domains; - private final Filter domainFilter = new Filter() { + private final Filter domainFilter = + new Filter() { - @Override - protected FilterResults performFiltering(final CharSequence constraint) { - final ImmutableList.Builder<String> builder = new ImmutableList.Builder<>(); - final String[] split = constraint == null ? new String[0] : constraint.toString().split("@"); - if (split.length == 1) { - final String local = split[0].toLowerCase(Locale.ENGLISH); - if (Config.QUICKSY_DOMAIN != null && E164_PATTERN.matcher(local).matches()) { - builder.add(local + '@' + Config.QUICKSY_DOMAIN.toEscapedString()); - } else { - for (String domain : domains) { - builder.add(local + '@' + domain); - } - } - } else if (split.length == 2) { - final String localPart = split[0].toLowerCase(Locale.ENGLISH); - final String domainPart = split[1].toLowerCase(Locale.ENGLISH); - if (domains.contains(domainPart)) { - return new FilterResults(); - } - for (final String domain : domains) { - if (domain.contains(domainPart)) { - builder.add(localPart + "@" + domain); + @Override + protected FilterResults performFiltering(final CharSequence constraint) { + final ImmutableList.Builder<String> builder = new ImmutableList.Builder<>(); + final String[] split = + constraint == null ? new String[0] : constraint.toString().split("@"); + if (split.length == 1) { + 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()); + } else { + for (String domain : domains) { + builder.add(local + '@' + domain); + } + } + } else if (split.length == 2) { + final String localPart = split[0].toLowerCase(Locale.ENGLISH); + final String domainPart = split[1].toLowerCase(Locale.ENGLISH); + if (domains.contains(domainPart)) { + return new FilterResults(); + } + for (final String domain : domains) { + if (domain.contains(domainPart)) { + builder.add(localPart + "@" + domain); + } + } + } else { + return new FilterResults(); } + final var suggestions = builder.build(); + final FilterResults filterResults = new FilterResults(); + filterResults.values = suggestions; + filterResults.count = suggestions.size(); + return filterResults; } - } else { - return new FilterResults(); - } - final var suggestions = builder.build(); - final FilterResults filterResults = new FilterResults(); - filterResults.values = suggestions; - filterResults.count = suggestions.size(); - return filterResults; - } - @Override - protected void publishResults(final CharSequence constraint, final FilterResults results) { - final ImmutableList.Builder<String> suggestions = new ImmutableList.Builder<>(); - if (results.values instanceof Collection<?> collection) { - for(final Object item : collection) { - if (item instanceof String string) { - suggestions.add(string); + @Override + protected void publishResults( + final CharSequence constraint, final FilterResults results) { + final ImmutableList.Builder<String> suggestions = new ImmutableList.Builder<>(); + if (results.values instanceof Collection<?> collection) { + for (final Object item : collection) { + if (item instanceof String string) { + suggestions.add(string); + } + } } + clear(); + addAll(suggestions.build()); + notifyDataSetChanged(); } - } - clear(); - addAll(suggestions.build()); - notifyDataSetChanged(); - } - }; + }; - public KnownHostsAdapter(final Context context, final int viewResourceId, final Collection<String> knownHosts) { + public KnownHostsAdapter( + final Context context, final int viewResourceId, final Collection<String> knownHosts) { super(context, viewResourceId, new ArrayList<>()); - domains = Ordering.natural().sortedCopy(knownHosts); + domains = Ordering.natural().sortedCopy(knownHosts); } public KnownHostsAdapter(final Context context, final int viewResourceId) { diff --git a/src/main/java/eu/siacs/conversations/ui/fragment/settings/UpSettingsFragment.java b/src/main/java/eu/siacs/conversations/ui/fragment/settings/UpSettingsFragment.java index 771acbbe1..2b279c905 100644 --- a/src/main/java/eu/siacs/conversations/ui/fragment/settings/UpSettingsFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/fragment/settings/UpSettingsFragment.java @@ -2,20 +2,16 @@ package eu.siacs.conversations.ui.fragment.settings; import android.os.Bundle; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.preference.EditTextPreference; import androidx.preference.ListPreference; - import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; - import eu.siacs.conversations.R; import eu.siacs.conversations.receiver.UnifiedPushDistributor; import eu.siacs.conversations.xmpp.Jid; - import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; @@ -31,29 +27,38 @@ public class UpSettingsFragment extends XmppPreferenceFragment { @Override public void onBackendConnected() { final ListPreference upAccounts = findPreference(UnifiedPushDistributor.PREFERENCE_ACCOUNT); - final EditTextPreference pushServer = findPreference(UnifiedPushDistributor.PREFERENCE_PUSH_SERVER); + final EditTextPreference pushServer = + findPreference(UnifiedPushDistributor.PREFERENCE_PUSH_SERVER); if (upAccounts == null || pushServer == null) { throw new IllegalStateException(); } - pushServer.setOnPreferenceChangeListener((preference, newValue) -> { - if (newValue instanceof String string) { - if (Strings.isNullOrEmpty(string) || isJidInvalid(string) || isHttpUri(string)) { - Toast.makeText(requireActivity(),R.string.invalid_jid,Toast.LENGTH_LONG).show(); - return false; - } else { - return true; - } - } else { - Toast.makeText(requireActivity(),R.string.invalid_jid,Toast.LENGTH_LONG).show(); - return false; - } - }); + pushServer.setOnPreferenceChangeListener( + (preference, newValue) -> { + if (newValue instanceof String string) { + if (Strings.isNullOrEmpty(string) + || isJidInvalid(string) + || isHttpUri(string)) { + Toast.makeText( + requireActivity(), + R.string.invalid_jid, + Toast.LENGTH_LONG) + .show(); + return false; + } else { + return true; + } + } else { + Toast.makeText(requireActivity(), R.string.invalid_jid, Toast.LENGTH_LONG) + .show(); + return false; + } + }); reconfigureUpAccountPreference(upAccounts); } private static boolean isJidInvalid(final String input) { try { - final var jid = Jid.ofEscaped(input); + final var jid = Jid.ofUserInput(input); return !jid.isBareJid(); } catch (final IllegalArgumentException e) { return true; @@ -67,16 +72,15 @@ public class UpSettingsFragment extends XmppPreferenceFragment { } catch (final URISyntaxException e) { return false; } - return Arrays.asList("http","https").contains(uri.getScheme()); + return Arrays.asList("http", "https").contains(uri.getScheme()); } - private void reconfigureUpAccountPreference(final ListPreference listPreference) { final List<CharSequence> accounts = ImmutableList.copyOf( Lists.transform( requireService().getAccounts(), - a -> a.getJid().asBareJid().toEscapedString())); + a -> a.getJid().asBareJid().toString())); final ImmutableList.Builder<CharSequence> entries = new ImmutableList.Builder<>(); final ImmutableList.Builder<CharSequence> entryValues = new ImmutableList.Builder<>(); entries.add(getString(R.string.no_account_deactivated)); diff --git a/src/main/java/eu/siacs/conversations/utils/AccountUtils.java b/src/main/java/eu/siacs/conversations/utils/AccountUtils.java index 4b2c2957f..bcfffe8c1 100644 --- a/src/main/java/eu/siacs/conversations/utils/AccountUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/AccountUtils.java @@ -5,20 +5,16 @@ import android.content.Intent; import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; - import com.google.common.primitives.Bytes; import com.google.common.primitives.Longs; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.XmppActivity; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; public class AccountUtils { @@ -50,9 +46,7 @@ public class AccountUtils { public static UUID createUuid4(long mostSigBits, long leastSigBits) { final byte[] bytes = - Bytes.concat( - Longs.toByteArray(mostSigBits), - Longs.toByteArray(leastSigBits)); + Bytes.concat(Longs.toByteArray(mostSigBits), Longs.toByteArray(leastSigBits)); bytes[6] &= 0x0f; /* clear version */ bytes[6] |= 0x40; /* set to version 4 */ bytes[8] &= 0x3f; /* clear variant */ @@ -65,7 +59,7 @@ public class AccountUtils { final ArrayList<String> accounts = new ArrayList<>(); for (final Account account : service.getAccounts()) { if (account.isEnabled()) { - accounts.add(account.getJid().asBareJid().toEscapedString()); + accounts.add(account.getJid().asBareJid().toString()); } } return accounts; diff --git a/src/main/java/eu/siacs/conversations/utils/BackupFileHeader.java b/src/main/java/eu/siacs/conversations/utils/BackupFileHeader.java index 3b536c27a..21ffeadda 100644 --- a/src/main/java/eu/siacs/conversations/utils/BackupFileHeader.java +++ b/src/main/java/eu/siacs/conversations/utils/BackupFileHeader.java @@ -1,13 +1,11 @@ package eu.siacs.conversations.utils; import androidx.annotation.NonNull; - +import eu.siacs.conversations.xmpp.Jid; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import eu.siacs.conversations.xmpp.Jid; - public class BackupFileHeader { private static final int VERSION = 2; @@ -18,17 +16,22 @@ public class BackupFileHeader { private final byte[] iv; private final byte[] salt; - @NonNull @Override public String toString() { - return "BackupFileHeader{" + - "app='" + app + '\'' + - ", jid=" + jid + - ", timestamp=" + timestamp + - ", iv=" + CryptoHelper.bytesToHex(iv) + - ", salt=" + CryptoHelper.bytesToHex(salt) + - '}'; + return "BackupFileHeader{" + + "app='" + + app + + '\'' + + ", jid=" + + jid + + ", timestamp=" + + timestamp + + ", iv=" + + CryptoHelper.bytesToHex(iv) + + ", salt=" + + CryptoHelper.bytesToHex(salt) + + '}'; } public BackupFileHeader(String app, Jid jid, long timestamp, byte[] iv, byte[] salt) { @@ -42,7 +45,7 @@ public class BackupFileHeader { public void write(DataOutputStream dataOutputStream) throws IOException { dataOutputStream.writeInt(VERSION); dataOutputStream.writeUTF(app); - dataOutputStream.writeUTF(jid.asBareJid().toEscapedString()); + dataOutputStream.writeUTF(jid.asBareJid().toString()); dataOutputStream.writeLong(timestamp); dataOutputStream.write(iv); dataOutputStream.write(salt); @@ -61,10 +64,13 @@ public class BackupFileHeader { throw new OutdatedBackupFileVersion(); } if (version != VERSION) { - throw new IllegalArgumentException("Backup File version was " + version + " but app only supports version " + VERSION); + throw new IllegalArgumentException( + "Backup File version was " + + version + + " but app only supports version " + + VERSION); } return new BackupFileHeader(app, Jid.of(jid), timestamp, iv, salt); - } public byte[] getSalt() { @@ -87,7 +93,5 @@ public class BackupFileHeader { return timestamp; } - public static class OutdatedBackupFileVersion extends RuntimeException { - - } + public static class OutdatedBackupFileVersion extends RuntimeException {} } diff --git a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java index 34f5caccf..6d6232b0b 100644 --- a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java @@ -5,14 +5,12 @@ import static eu.siacs.conversations.utils.Random.SECURE_RANDOM; import android.os.Bundle; import android.util.Base64; import android.util.Pair; - import androidx.annotation.StringRes; - -import org.bouncycastle.asn1.x500.X500Name; -import org.bouncycastle.asn1.x500.style.BCStyle; -import org.bouncycastle.asn1.x500.style.IETFUtils; -import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; - +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.xmpp.Jid; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -28,22 +26,22 @@ import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.regex.Pattern; - -import eu.siacs.conversations.Config; -import eu.siacs.conversations.R; -import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.entities.Message; -import eu.siacs.conversations.xmpp.Jid; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.asn1.x500.style.IETFUtils; +import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; public final class CryptoHelper { - public static final Pattern UUID_PATTERN = Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"); - final public static byte[] ONE = new byte[]{0, 0, 0, 1}; - private static final char[] CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz123456789+-/#$!?".toCharArray(); + public static final Pattern UUID_PATTERN = + Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"); + public static final byte[] ONE = new byte[] {0, 0, 0, 1}; + private static final char[] CHARS = + "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz123456789+-/#$!?".toCharArray(); private static final int PW_LENGTH = 12; private static final char[] VOWELS = "aeiou".toCharArray(); private static final char[] CONSONANTS = "bcfghjklmnpqrstvwxyz".toCharArray(); - private final static char[] hexArray = "0123456789abcdef".toCharArray(); + private static final char[] hexArray = "0123456789abcdef".toCharArray(); public static String bytesToHex(byte[] bytes) { char[] hexChars = new char[bytes.length * 2]; @@ -68,7 +66,10 @@ public final class CryptoHelper { char[] output = new char[rand * 2 + (5 - rand)]; boolean vowel = SECURE_RANDOM.nextBoolean(); for (int i = 0; i < output.length; ++i) { - output[i] = vowel ? VOWELS[SECURE_RANDOM.nextInt(VOWELS.length)] : CONSONANTS[SECURE_RANDOM.nextInt(CONSONANTS.length)]; + output[i] = + vowel + ? VOWELS[SECURE_RANDOM.nextInt(VOWELS.length)] + : CONSONANTS[SECURE_RANDOM.nextInt(CONSONANTS.length)]; vowel = !vowel; } return String.valueOf(output); @@ -78,8 +79,10 @@ public final class CryptoHelper { int len = hexString.length(); byte[] array = new byte[len / 2]; for (int i = 0; i < len; i += 2) { - array[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character - .digit(hexString.charAt(i + 1), 16)); + array[i / 2] = + (byte) + ((Character.digit(hexString.charAt(i), 16) << 4) + + Character.digit(hexString.charAt(i + 1), 16)); } return array; } @@ -95,9 +98,7 @@ public final class CryptoHelper { return result; } - /** - * Escapes usernames or passwords for SASL. - */ + /** Escapes usernames or passwords for SASL. */ public static String saslEscape(final String s) { final StringBuilder sb = new StringBuilder((int) (s.length() * 1.1)); for (int i = 0; i < s.length(); i++) { @@ -149,7 +150,8 @@ public final class CryptoHelper { } public static String[] getOrderedCipherSuites(final String[] platformSupportedCipherSuites) { - final Collection<String> cipherSuites = new LinkedHashSet<>(Arrays.asList(Config.ENABLED_CIPHERS)); + final Collection<String> cipherSuites = + new LinkedHashSet<>(Arrays.asList(Config.ENABLED_CIPHERS)); final List<String> platformCiphers = Arrays.asList(platformSupportedCipherSuites); cipherSuites.retainAll(platformCiphers); cipherSuites.addAll(platformCiphers); @@ -172,7 +174,10 @@ public final class CryptoHelper { } } - public static Pair<Jid, String> extractJidAndName(X509Certificate certificate) throws CertificateEncodingException, IllegalArgumentException, CertificateParsingException { + public static Pair<Jid, String> extractJidAndName(X509Certificate certificate) + throws CertificateEncodingException, + IllegalArgumentException, + CertificateParsingException { Collection<List<?>> alternativeNames = certificate.getSubjectAlternativeNames(); List<String> emails = new ArrayList<>(); if (alternativeNames != null) { @@ -185,9 +190,15 @@ public final class CryptoHelper { } X500Name x500name = new JcaX509CertificateHolder(certificate).getSubject(); if (emails.size() == 0 && x500name.getRDNs(BCStyle.EmailAddress).length > 0) { - emails.add(IETFUtils.valueToString(x500name.getRDNs(BCStyle.EmailAddress)[0].getFirst().getValue())); + emails.add( + IETFUtils.valueToString( + x500name.getRDNs(BCStyle.EmailAddress)[0].getFirst().getValue())); } - String name = x500name.getRDNs(BCStyle.CN).length > 0 ? IETFUtils.valueToString(x500name.getRDNs(BCStyle.CN)[0].getFirst().getValue()) : null; + String name = + x500name.getRDNs(BCStyle.CN).length > 0 + ? IETFUtils.valueToString( + x500name.getRDNs(BCStyle.CN)[0].getFirst().getValue()) + : null; if (emails.size() >= 1) { return new Pair<>(Jid.of(emails.get(0)), name); } else if (name != null) { @@ -209,26 +220,33 @@ public final class CryptoHelper { JcaX509CertificateHolder holder = new JcaX509CertificateHolder(certificate); X500Name subject = holder.getSubject(); try { - information.putString("subject_cn", subject.getRDNs(BCStyle.CN)[0].getFirst().getValue().toString()); + information.putString( + "subject_cn", + subject.getRDNs(BCStyle.CN)[0].getFirst().getValue().toString()); } catch (Exception e) { - //ignored + // ignored } try { - information.putString("subject_o", subject.getRDNs(BCStyle.O)[0].getFirst().getValue().toString()); + information.putString( + "subject_o", + subject.getRDNs(BCStyle.O)[0].getFirst().getValue().toString()); } catch (Exception e) { - //ignored + // ignored } X500Name issuer = holder.getIssuer(); try { - information.putString("issuer_cn", issuer.getRDNs(BCStyle.CN)[0].getFirst().getValue().toString()); + information.putString( + "issuer_cn", + issuer.getRDNs(BCStyle.CN)[0].getFirst().getValue().toString()); } catch (Exception e) { - //ignored + // ignored } try { - information.putString("issuer_o", issuer.getRDNs(BCStyle.O)[0].getFirst().getValue().toString()); + information.putString( + "issuer_o", issuer.getRDNs(BCStyle.O)[0].getFirst().getValue().toString()); } catch (Exception e) { - //ignored + // ignored } try { information.putString("sha1", getFingerprintCert(certificate.getEncoded())); @@ -248,7 +266,7 @@ public final class CryptoHelper { } public static String getFingerprint(Jid jid, String androidId) { - return getFingerprint(jid.toEscapedString() + "\00" + androidId); + return getFingerprint(jid.toString() + "\00" + androidId); } public static String getAccountFingerprint(Account account, String androidId) { @@ -268,8 +286,9 @@ public final class CryptoHelper { return switch (encryption) { case Message.ENCRYPTION_OTR -> R.string.encryption_choice_otr; case Message.ENCRYPTION_AXOLOTL, - Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE, - Message.ENCRYPTION_AXOLOTL_FAILED -> R.string.encryption_choice_omemo; + Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE, + Message.ENCRYPTION_AXOLOTL_FAILED -> + R.string.encryption_choice_omemo; case Message.ENCRYPTION_PGP -> R.string.encryption_choice_pgp; default -> R.string.encryption_choice_unencrypted; }; @@ -280,6 +299,8 @@ public final class CryptoHelper { return false; } final String u = url.toLowerCase(); - return !u.contains(" ") && (u.startsWith("https://") || u.startsWith("http://") || u.startsWith("p1s3://")) && u.endsWith(".pgp"); + return !u.contains(" ") + && (u.startsWith("https://") || u.startsWith("http://") || u.startsWith("p1s3://")) + && u.endsWith(".pgp"); } } diff --git a/src/main/java/eu/siacs/conversations/utils/IrregularUnicodeDetector.java b/src/main/java/eu/siacs/conversations/utils/IrregularUnicodeDetector.java index 5cc093249..9dcdbb15e 100644 --- a/src/main/java/eu/siacs/conversations/utils/IrregularUnicodeDetector.java +++ b/src/main/java/eu/siacs/conversations/utils/IrregularUnicodeDetector.java @@ -37,11 +37,9 @@ import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.style.ForegroundColorSpan; import android.util.LruCache; - import androidx.annotation.ColorInt; - import com.google.android.material.color.MaterialColors; - +import eu.siacs.conversations.xmpp.Jid; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -54,224 +52,234 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; -import eu.siacs.conversations.R; -import eu.siacs.conversations.xmpp.Jid; - public class IrregularUnicodeDetector { - private static final Map<Character.UnicodeBlock, Character.UnicodeBlock> NORMALIZATION_MAP; - private static final LruCache<Jid, PatternTuple> CACHE = new LruCache<>(4096); - private static final List<String> AMBIGUOUS_CYRILLIC = Arrays.asList("а","г","е","Ñ•","Ñ–","ј","Ä·","Ô›","о","Ñ€","Ñ","у","Ñ…"); - - static { - Map<Character.UnicodeBlock, Character.UnicodeBlock> temp = new HashMap<>(); - temp.put(Character.UnicodeBlock.LATIN_1_SUPPLEMENT, Character.UnicodeBlock.BASIC_LATIN); - NORMALIZATION_MAP = Collections.unmodifiableMap(temp); - } + private static final Map<Character.UnicodeBlock, Character.UnicodeBlock> NORMALIZATION_MAP; + private static final LruCache<Jid, PatternTuple> CACHE = new LruCache<>(4096); + private static final List<String> AMBIGUOUS_CYRILLIC = + Arrays.asList("а", "г", "е", "Ñ•", "Ñ–", "ј", "Ä·", "Ô›", "о", "Ñ€", "Ñ", "у", "Ñ…"); - private static Character.UnicodeBlock normalize(Character.UnicodeBlock in) { - if (NORMALIZATION_MAP.containsKey(in)) { - return NORMALIZATION_MAP.get(in); - } else { - return in; - } - } + static { + Map<Character.UnicodeBlock, Character.UnicodeBlock> temp = new HashMap<>(); + temp.put(Character.UnicodeBlock.LATIN_1_SUPPLEMENT, Character.UnicodeBlock.BASIC_LATIN); + NORMALIZATION_MAP = Collections.unmodifiableMap(temp); + } - public static Spannable style(final Context context, Jid jid) { - return style(jid, MaterialColors.getColor(context, com.google.android.material.R.attr.colorError,"colorError not found")); - } + private static Character.UnicodeBlock normalize(Character.UnicodeBlock in) { + if (NORMALIZATION_MAP.containsKey(in)) { + return NORMALIZATION_MAP.get(in); + } else { + return in; + } + } - private static Spannable style(Jid jid, @ColorInt int color) { - PatternTuple patternTuple = find(jid); - SpannableStringBuilder builder = new SpannableStringBuilder(); - if (jid.getEscapedLocal() != null && patternTuple.local != null) { - SpannableString local = new SpannableString(jid.getEscapedLocal()); - colorize(local, patternTuple.local, color); - builder.append(local); - builder.append('@'); - } - if (jid.getDomain() != null) { - String[] labels = jid.getDomain().toEscapedString().split("\\."); - for (int i = 0; i < labels.length; ++i) { - SpannableString spannableString = new SpannableString(labels[i]); - colorize(spannableString, patternTuple.domain.get(i), color); - if (i != 0) { - builder.append('.'); - } - builder.append(spannableString); - } - } - if (builder.length() != 0 && jid.getResource() != null) { - builder.append('/'); - builder.append(jid.getResource()); - } - return builder; - } + public static Spannable style(final Context context, Jid jid) { + return style( + jid, + MaterialColors.getColor( + context, + com.google.android.material.R.attr.colorError, + "colorError not found")); + } - private static void colorize(SpannableString spannableString, Pattern pattern, @ColorInt int color) { - Matcher matcher = pattern.matcher(spannableString); - while (matcher.find()) { - if (matcher.start() < matcher.end()) { - spannableString.setSpan(new ForegroundColorSpan(color), matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } - } + private static Spannable style(Jid jid, @ColorInt int color) { + PatternTuple patternTuple = find(jid); + SpannableStringBuilder builder = new SpannableStringBuilder(); + if (jid.getLocal() != null && patternTuple.local != null) { + SpannableString local = new SpannableString(jid.getLocal()); + colorize(local, patternTuple.local, color); + builder.append(local); + builder.append('@'); + } + if (jid.getDomain() != null) { + String[] labels = jid.getDomain().toString().split("\\."); + for (int i = 0; i < labels.length; ++i) { + SpannableString spannableString = new SpannableString(labels[i]); + colorize(spannableString, patternTuple.domain.get(i), color); + if (i != 0) { + builder.append('.'); + } + builder.append(spannableString); + } + } + if (builder.length() != 0 && jid.getResource() != null) { + builder.append('/'); + builder.append(jid.getResource()); + } + return builder; + } - private static Map<Character.UnicodeBlock, List<String>> mapCompat(String word) { - Map<Character.UnicodeBlock, List<String>> map = new HashMap<>(); - final int length = word.length(); - for (int offset = 0; offset < length; ) { - final int codePoint = word.codePointAt(offset); - offset += Character.charCount(codePoint); - if (!Character.isLetter(codePoint)) { - continue; - } - Character.UnicodeBlock block = normalize(Character.UnicodeBlock.of(codePoint)); - List<String> codePoints; - if (map.containsKey(block)) { - codePoints = map.get(block); - } else { - codePoints = new ArrayList<>(); - map.put(block, codePoints); - } - codePoints.add(String.copyValueOf(Character.toChars(codePoint))); - } - return map; - } + private static void colorize( + SpannableString spannableString, Pattern pattern, @ColorInt int color) { + Matcher matcher = pattern.matcher(spannableString); + while (matcher.find()) { + if (matcher.start() < matcher.end()) { + spannableString.setSpan( + new ForegroundColorSpan(color), + matcher.start(), + matcher.end(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + } - @TargetApi(Build.VERSION_CODES.N) - private static Map<Character.UnicodeScript, List<String>> map(String word) { - Map<Character.UnicodeScript, List<String>> map = new HashMap<>(); - final int length = word.length(); - for (int offset = 0; offset < length; ) { - final int codePoint = word.codePointAt(offset); - Character.UnicodeScript script = Character.UnicodeScript.of(codePoint); - if (script != Character.UnicodeScript.COMMON) { - List<String> codePoints; - if (map.containsKey(script)) { - codePoints = map.get(script); - } else { - codePoints = new ArrayList<>(); - map.put(script, codePoints); - } - codePoints.add(String.copyValueOf(Character.toChars(codePoint))); - } - offset += Character.charCount(codePoint); - } - return map; - } + private static Map<Character.UnicodeBlock, List<String>> mapCompat(String word) { + Map<Character.UnicodeBlock, List<String>> map = new HashMap<>(); + final int length = word.length(); + for (int offset = 0; offset < length; ) { + final int codePoint = word.codePointAt(offset); + offset += Character.charCount(codePoint); + if (!Character.isLetter(codePoint)) { + continue; + } + Character.UnicodeBlock block = normalize(Character.UnicodeBlock.of(codePoint)); + List<String> codePoints; + if (map.containsKey(block)) { + codePoints = map.get(block); + } else { + codePoints = new ArrayList<>(); + map.put(block, codePoints); + } + codePoints.add(String.copyValueOf(Character.toChars(codePoint))); + } + return map; + } - private static Set<String> eliminateFirstAndGetCodePointsCompat(Map<Character.UnicodeBlock, List<String>> map) { - return eliminateFirstAndGetCodePoints(map, Character.UnicodeBlock.BASIC_LATIN); - } + @TargetApi(Build.VERSION_CODES.N) + private static Map<Character.UnicodeScript, List<String>> map(String word) { + Map<Character.UnicodeScript, List<String>> map = new HashMap<>(); + final int length = word.length(); + for (int offset = 0; offset < length; ) { + final int codePoint = word.codePointAt(offset); + Character.UnicodeScript script = Character.UnicodeScript.of(codePoint); + if (script != Character.UnicodeScript.COMMON) { + List<String> codePoints; + if (map.containsKey(script)) { + codePoints = map.get(script); + } else { + codePoints = new ArrayList<>(); + map.put(script, codePoints); + } + codePoints.add(String.copyValueOf(Character.toChars(codePoint))); + } + offset += Character.charCount(codePoint); + } + return map; + } - @TargetApi(Build.VERSION_CODES.N) - private static Set<String> eliminateFirstAndGetCodePoints(Map<Character.UnicodeScript, List<String>> map) { - return eliminateFirstAndGetCodePoints(map, Character.UnicodeScript.COMMON); - } + private static Set<String> eliminateFirstAndGetCodePointsCompat( + Map<Character.UnicodeBlock, List<String>> map) { + return eliminateFirstAndGetCodePoints(map, Character.UnicodeBlock.BASIC_LATIN); + } - private static <T> Set<String> eliminateFirstAndGetCodePoints(Map<T, List<String>> map, T defaultPick) { - T pick = defaultPick; - int size = 0; - for (Map.Entry<T, List<String>> entry : map.entrySet()) { - if (entry.getValue().size() > size) { - size = entry.getValue().size(); - pick = entry.getKey(); - } - } - map.remove(pick); - Set<String> all = new HashSet<>(); - for (List<String> codePoints : map.values()) { - all.addAll(codePoints); - } - return all; - } + @TargetApi(Build.VERSION_CODES.N) + private static Set<String> eliminateFirstAndGetCodePoints( + Map<Character.UnicodeScript, List<String>> map) { + return eliminateFirstAndGetCodePoints(map, Character.UnicodeScript.COMMON); + } - private static Set<String> findIrregularCodePoints(String word) { - Set<String> codePoints; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - final Map<Character.UnicodeBlock, List<String>> map = mapCompat(word); - final Set<String> set = asSet(map); - if (containsOnlyAmbiguousCyrillic(set)) { - return set; - } - codePoints = eliminateFirstAndGetCodePointsCompat(map); - } else { - final Map<Character.UnicodeScript, List<String>> map = map(word); - final Set<String> set = asSet(map); - if (containsOnlyAmbiguousCyrillic(set)) { - return set; - } - codePoints = eliminateFirstAndGetCodePoints(map); - } - return codePoints; - } + private static <T> Set<String> eliminateFirstAndGetCodePoints( + Map<T, List<String>> map, T defaultPick) { + T pick = defaultPick; + int size = 0; + for (Map.Entry<T, List<String>> entry : map.entrySet()) { + if (entry.getValue().size() > size) { + size = entry.getValue().size(); + pick = entry.getKey(); + } + } + map.remove(pick); + Set<String> all = new HashSet<>(); + for (List<String> codePoints : map.values()) { + all.addAll(codePoints); + } + return all; + } - private static Set<String> asSet(Map<?, List<String>> map) { - final Set<String> flat = new HashSet<>(); - for(List<String> value : map.values()) { - flat.addAll(value); - } - return flat; - } + private static Set<String> findIrregularCodePoints(String word) { + Set<String> codePoints; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + final Map<Character.UnicodeBlock, List<String>> map = mapCompat(word); + final Set<String> set = asSet(map); + if (containsOnlyAmbiguousCyrillic(set)) { + return set; + } + codePoints = eliminateFirstAndGetCodePointsCompat(map); + } else { + final Map<Character.UnicodeScript, List<String>> map = map(word); + final Set<String> set = asSet(map); + if (containsOnlyAmbiguousCyrillic(set)) { + return set; + } + codePoints = eliminateFirstAndGetCodePoints(map); + } + return codePoints; + } + private static Set<String> asSet(Map<?, List<String>> map) { + final Set<String> flat = new HashSet<>(); + for (List<String> value : map.values()) { + flat.addAll(value); + } + return flat; + } - private static boolean containsOnlyAmbiguousCyrillic(Collection<String> codePoints) { - for (String codePoint : codePoints) { - if (!AMBIGUOUS_CYRILLIC.contains(codePoint)) { - return false; - } - } - return true; - } + private static boolean containsOnlyAmbiguousCyrillic(Collection<String> codePoints) { + for (String codePoint : codePoints) { + if (!AMBIGUOUS_CYRILLIC.contains(codePoint)) { + return false; + } + } + return true; + } - private static PatternTuple find(Jid jid) { - synchronized (CACHE) { - PatternTuple pattern = CACHE.get(jid); - if (pattern != null) { - return pattern; - } + private static PatternTuple find(Jid jid) { + synchronized (CACHE) { + PatternTuple pattern = CACHE.get(jid); + if (pattern != null) { + return pattern; + } pattern = PatternTuple.of(jid); - CACHE.put(jid, pattern); - return pattern; - } - } + CACHE.put(jid, pattern); + return pattern; + } + } - private static Pattern create(Set<String> codePoints) { - final StringBuilder pattern = new StringBuilder(); - for (String codePoint : codePoints) { - if (pattern.length() != 0) { - pattern.append('|'); - } - pattern.append(Pattern.quote(codePoint)); - } - return Pattern.compile(pattern.toString()); - } + private static Pattern create(Set<String> codePoints) { + final StringBuilder pattern = new StringBuilder(); + for (String codePoint : codePoints) { + if (pattern.length() != 0) { + pattern.append('|'); + } + pattern.append(Pattern.quote(codePoint)); + } + return Pattern.compile(pattern.toString()); + } - private static class PatternTuple { - private final Pattern local; - private final List<Pattern> domain; + private static class PatternTuple { + private final Pattern local; + private final List<Pattern> domain; - private PatternTuple(Pattern local, List<Pattern> domain) { - this.local = local; - this.domain = domain; - } + private PatternTuple(Pattern local, List<Pattern> domain) { + this.local = local; + this.domain = domain; + } - private static PatternTuple of(Jid jid) { - final Pattern localPattern; - if (jid.getEscapedLocal() != null) { - localPattern = create(findIrregularCodePoints(jid.getEscapedLocal())); - } else { - localPattern = null; - } - String domain = jid.getDomain().toEscapedString(); - final List<Pattern> domainPatterns = new ArrayList<>(); - if (domain != null) { - for (String label : domain.split("\\.")) { - domainPatterns.add(create(findIrregularCodePoints(label))); - } - } - return new PatternTuple(localPattern, domainPatterns); - } - } + private static PatternTuple of(Jid jid) { + final Pattern localPattern; + if (jid.getLocal() != null) { + localPattern = create(findIrregularCodePoints(jid.getLocal())); + } else { + localPattern = null; + } + String domain = jid.getDomain().toString(); + final List<Pattern> domainPatterns = new ArrayList<>(); + if (domain != null) { + for (String label : domain.split("\\.")) { + domainPatterns.add(create(findIrregularCodePoints(label))); + } + } + return new PatternTuple(localPattern, domainPatterns); + } + } } diff --git a/src/main/java/eu/siacs/conversations/utils/JidHelper.java b/src/main/java/eu/siacs/conversations/utils/JidHelper.java index e7443c8bb..aac7d156b 100644 --- a/src/main/java/eu/siacs/conversations/utils/JidHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/JidHelper.java @@ -29,22 +29,19 @@ package eu.siacs.conversations.utils; - +import eu.siacs.conversations.Config; +import eu.siacs.conversations.xmpp.Jid; import java.util.Arrays; import java.util.List; import java.util.Locale; -import eu.siacs.conversations.Config; -import eu.siacs.conversations.xmpp.InvalidJid; -import eu.siacs.conversations.xmpp.Jid; - public class JidHelper { private static final List<String> LOCAL_PART_BLACKLIST = Arrays.asList("xmpp", "jabber", "me"); public static String localPartOrFallback(Jid jid) { if (LOCAL_PART_BLACKLIST.contains(jid.getLocal().toLowerCase(Locale.ENGLISH))) { - final String domain = jid.getDomain().toEscapedString(); + final String domain = jid.getDomain().toString(); final int index = domain.indexOf('.'); return index > 1 ? domain.substring(0, index) : domain; } else { @@ -52,16 +49,7 @@ public class JidHelper { } } - public static Jid parseOrFallbackToInvalid(String jid) { - try { - return Jid.of(jid); - } catch (IllegalArgumentException e) { - return InvalidJid.of(jid, true); - } - } - public static boolean isQuicksyDomain(final Jid jid) { return Config.QUICKSY_DOMAIN != null && Config.QUICKSY_DOMAIN.equals(jid.getDomain()); } - } diff --git a/src/main/java/eu/siacs/conversations/utils/XmppUri.java b/src/main/java/eu/siacs/conversations/utils/XmppUri.java index 6c3075be9..b6022cb69 100644 --- a/src/main/java/eu/siacs/conversations/utils/XmppUri.java +++ b/src/main/java/eu/siacs/conversations/utils/XmppUri.java @@ -1,14 +1,12 @@ package eu.siacs.conversations.utils; import android.net.Uri; - import androidx.annotation.NonNull; - import com.google.common.base.CharMatcher; import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; - +import eu.siacs.conversations.xmpp.Jid; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.ArrayList; @@ -18,8 +16,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import eu.siacs.conversations.xmpp.Jid; - public class XmppUri { public static final String ACTION_JOIN = "join"; @@ -42,8 +38,8 @@ public class XmppUri { parse(Uri.parse(uri)); } catch (IllegalArgumentException e) { try { - jid = Jid.ofEscaped(uri).asBareJid().toEscapedString(); - } catch (IllegalArgumentException e2) { + jid = Jid.of(uri).asBareJid().toString(); + } catch (final IllegalArgumentException e2) { jid = null; } } @@ -60,7 +56,8 @@ public class XmppUri { private static Map<String, String> parseParameters(final String query, final char seperator) { final ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<>(); - final String[] pairs = query == null ? new String[0] : query.split(String.valueOf(seperator)); + final String[] pairs = + query == null ? new String[0] : query.split(String.valueOf(seperator)); for (String pair : pairs) { final String[] parts = pair.split("=", 2); if (parts.length == 0) { @@ -94,7 +91,7 @@ public class XmppUri { final int id = Integer.parseInt(key.substring(OMEMO_URI_PARAM.length())); builder.add(new Fingerprint(FingerprintType.OMEMO, value, id)); } catch (Exception e) { - //ignoring invalid device id + // ignoring invalid device id } } else if ("omemo".equals(key)) { builder.add(new Fingerprint(FingerprintType.OMEMO, value, 0)); @@ -103,7 +100,8 @@ public class XmppUri { return builder.build(); } - public static String getFingerprintUri(final String base, final List<XmppUri.Fingerprint> fingerprints, char separator) { + public static String getFingerprintUri( + final String base, final List<XmppUri.Fingerprint> fingerprints, char separator) { final StringBuilder builder = new StringBuilder(base); builder.append('?'); for (int i = 0; i < fingerprints.size(); ++i) { @@ -145,8 +143,8 @@ public class XmppUri { if (segments.size() >= 2 && segments.get(1).contains("@")) { // sample : https://conversations.im/i/foo@bar.com try { - jid = Jid.ofEscaped(lameUrlDecode(segments.get(1))).toEscapedString(); - } catch (Exception e) { + jid = Jid.of(lameUrlDecode(segments.get(1))).toString(); + } catch (final Exception e) { jid = null; } } else if (segments.size() >= 3) { @@ -172,7 +170,8 @@ public class XmppUri { } } this.fingerprints = parseFingerprints(parameters); - } else if ("imto".equalsIgnoreCase(scheme) && Arrays.asList("xmpp", "jabber").contains(uri.getHost())) { + } else if ("imto".equalsIgnoreCase(scheme) + && Arrays.asList("xmpp", "jabber").contains(uri.getHost())) { // sample: imto://xmpp/foo@bar.com try { jid = URLDecoder.decode(uri.getEncodedPath(), "UTF-8").split("/")[1].trim(); @@ -195,15 +194,18 @@ public class XmppUri { public boolean isAction(final String action) { return Collections2.transform( - parameters.keySet(), - s -> CharMatcher.inRange('a', 'z').or(CharMatcher.inRange('A', 'Z')).retainFrom(s) - ).contains(action); + parameters.keySet(), + s -> + CharMatcher.inRange('a', 'z') + .or(CharMatcher.inRange('A', 'Z')) + .retainFrom(s)) + .contains(action); } public Jid getJid() { try { - return this.jid == null ? null : Jid.ofEscaped(this.jid); - } catch (IllegalArgumentException e) { + return this.jid == null ? null : Jid.ofUserInput(this.jid); + } catch (final IllegalArgumentException e) { return null; } } @@ -213,9 +215,9 @@ public class XmppUri { return false; } try { - Jid.ofEscaped(jid); + Jid.ofUserInput(jid); return true; - } catch (IllegalArgumentException e) { + } catch (final IllegalArgumentException e) { return false; } } @@ -237,7 +239,7 @@ public class XmppUri { } public boolean hasFingerprints() { - return fingerprints.size() > 0; + return !fingerprints.isEmpty(); } public enum FingerprintType { diff --git a/src/main/java/eu/siacs/conversations/worker/ExportBackupWorker.java b/src/main/java/eu/siacs/conversations/worker/ExportBackupWorker.java index 02fc16326..d848ca54e 100644 --- a/src/main/java/eu/siacs/conversations/worker/ExportBackupWorker.java +++ b/src/main/java/eu/siacs/conversations/worker/ExportBackupWorker.java @@ -13,19 +13,16 @@ import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.os.SystemClock; import android.util.Log; - import androidx.annotation.NonNull; import androidx.core.app.NotificationCompat; import androidx.work.ForegroundInfo; import androidx.work.WorkManager; import androidx.work.Worker; import androidx.work.WorkerParameters; - import com.google.common.base.Optional; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.gson.stream.JsonWriter; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore; @@ -36,7 +33,6 @@ import eu.siacs.conversations.persistance.DatabaseBackend; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.utils.BackupFileHeader; import eu.siacs.conversations.utils.Compatibility; - import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; @@ -56,7 +52,6 @@ import java.util.Date; import java.util.List; import java.util.Locale; import java.util.zip.GZIPOutputStream; - import javax.crypto.Cipher; import javax.crypto.CipherOutputStream; import javax.crypto.NoSuchPaddingException; @@ -162,7 +157,8 @@ public class ExportBackupWorker extends Worker { Log.d( Config.LOGTAG, String.format( - "skipping backup for %s because password is empty. unable to encrypt", + "skipping backup for %s because password is empty. unable to" + + " encrypt", account.getJid().asBareJid())); count++; continue; @@ -170,7 +166,7 @@ public class ExportBackupWorker extends Worker { final String filename = String.format( "%s.%s.ceb", - account.getJid().asBareJid().toEscapedString(), + account.getJid().asBareJid().toString(), DATE_FORMAT.format(new Date())); final File file = new File(FileBackend.getBackupDirectory(context), filename); try { @@ -379,7 +375,9 @@ public class ExportBackupWorker extends Worker { getApplicationContext().getSystemService(NotificationManager.class); try (final Cursor cursor = db.rawQuery( - "select messages.* from messages join conversations on conversations.uuid=messages.conversationUuid where conversations.accountUuid=?", + "select messages.* from messages join conversations on" + + " conversations.uuid=messages.conversationUuid where" + + " conversations.accountUuid=?", new String[] {uuid})) { final int size = cursor != null ? cursor.getCount() : 0; Log.d(Config.LOGTAG, "exporting " + size + " messages for account " + uuid); diff --git a/src/main/java/eu/siacs/conversations/xml/Element.java b/src/main/java/eu/siacs/conversations/xml/Element.java index 20db7f878..1b91eeaae 100644 --- a/src/main/java/eu/siacs/conversations/xml/Element.java +++ b/src/main/java/eu/siacs/conversations/xml/Element.java @@ -1,20 +1,16 @@ package eu.siacs.conversations.xml; import androidx.annotation.NonNull; - import com.google.common.base.Optional; import com.google.common.base.Strings; import com.google.common.primitives.Ints; import com.google.common.primitives.Longs; - -import java.util.ArrayList; -import java.util.Hashtable; -import java.util.List; - import eu.siacs.conversations.utils.XmlHelper; -import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; import im.conversations.android.xmpp.model.stanza.Message; +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.List; public class Element { private final String name; @@ -133,7 +129,7 @@ public class Element { public Element setAttribute(String name, Jid value) { if (name != null && value != null) { - this.attributes.put(name, value.toEscapedString()); + this.attributes.put(name, value.toString()); } return this; } @@ -172,16 +168,12 @@ public class Element { return Optional.fromNullable(Ints.tryParse(value)); } - public Jid getAttributeAsJid(String name) { + public Jid getAttributeAsJid(final String name) { final String jid = this.getAttribute(name); - if (jid != null && !jid.isEmpty()) { - try { - return Jid.ofEscaped(jid); - } catch (final IllegalArgumentException e) { - return InvalidJid.of(jid, this instanceof Message); - } + if (Strings.isNullOrEmpty(jid)) { + return null; } - return null; + return Jid.ofOrInvalid(jid, this instanceof Message); } public Hashtable<String, String> getAttributes() { diff --git a/src/main/java/eu/siacs/conversations/xmpp/InvalidJid.java b/src/main/java/eu/siacs/conversations/xmpp/InvalidJid.java deleted file mode 100644 index 4e3092821..000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/InvalidJid.java +++ /dev/null @@ -1,155 +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.xmpp; - -import androidx.annotation.NonNull; - -import im.conversations.android.xmpp.model.stanza.Stanza; - -public class InvalidJid implements Jid { - - private final String value; - - private InvalidJid(String jid) { - this.value = jid; - } - - public static Jid of(String jid, boolean fallback) { - final int pos = jid.indexOf('/'); - if (fallback && pos >= 0 && jid.length() >= pos + 1) { - if (jid.substring(pos+1).trim().isEmpty()) { - return Jid.ofEscaped(jid.substring(0,pos)); - } - } - return new InvalidJid(jid); - } - - @Override - @NonNull - public String toString() { - return value; - } - - @Override - public boolean isFullJid() { - throw new AssertionError("Not implemented"); - } - - @Override - public boolean isBareJid() { - throw new AssertionError("Not implemented"); - } - - @Override - public boolean isDomainJid() { - throw new AssertionError("Not implemented"); - } - - @Override - public Jid asBareJid() { - throw new AssertionError("Not implemented"); - } - - - @Override - public Jid withResource(CharSequence charSequence) { - throw new AssertionError("Not implemented"); - } - - @Override - public String getLocal() { - throw new AssertionError("Not implemented"); - } - - @Override - public String getEscapedLocal() { - throw new AssertionError("Not implemented"); - } - - @Override - public Jid getDomain() { - throw new AssertionError("Not implemented"); - } - - @Override - public String getResource() { - throw new AssertionError("Not implemented"); - } - - @Override - public String toEscapedString() { - throw new AssertionError("Not implemented"); - } - - @Override - public int length() { - return value.length(); - } - - @Override - public char charAt(int index) { - return value.charAt(index); - } - - @Override - public CharSequence subSequence(int start, int end) { - return value.subSequence(start, end); - } - - @Override - public int compareTo(@NonNull Jid o) { - throw new AssertionError("Not implemented"); - } - - public static Jid getNullForInvalid(Jid jid) { - if (jid instanceof InvalidJid) { - return null; - } else { - return jid; - } - } - - public static boolean isValid(Jid jid) { - return !(jid instanceof InvalidJid); - } - - public static boolean hasValidFrom(Stanza stanza) { - final String from = stanza.getAttribute("from"); - if (from == null) { - return false; - } - try { - Jid.ofEscaped(from); - return true; - } catch (IllegalArgumentException e) { - return false; - } - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/Jid.java b/src/main/java/eu/siacs/conversations/xmpp/Jid.java index 299c872b3..34fbb9abb 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/Jid.java +++ b/src/main/java/eu/siacs/conversations/xmpp/Jid.java @@ -1,20 +1,19 @@ package eu.siacs.conversations.xmpp; +import androidx.annotation.NonNull; +import com.google.common.base.CharMatcher; +import im.conversations.android.xmpp.model.stanza.Stanza; +import java.io.Serializable; import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.jid.parts.Domainpart; import org.jxmpp.jid.parts.Localpart; import org.jxmpp.jid.parts.Resourcepart; import org.jxmpp.stringprep.XmppStringprepException; -import java.io.Serializable; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public interface Jid extends Comparable<Jid>, Serializable, CharSequence { - - Pattern JID = Pattern.compile("^((.*?)@)?([^/@]+)(/(.*))?$"); +public abstract class Jid implements Comparable<Jid>, Serializable, CharSequence { - static Jid of(CharSequence local, CharSequence domain, CharSequence resource) { + public static Jid of( + final CharSequence local, final CharSequence domain, final CharSequence resource) { if (local == null) { if (resource == null) { return ofDomain(domain); @@ -26,120 +25,312 @@ public interface Jid extends Comparable<Jid>, Serializable, CharSequence { return ofLocalAndDomain(local, domain); } try { - return new WrappedJid(JidCreate.entityFullFrom( - Localpart.fromUnescaped(local.toString()), - Domainpart.from(domain.toString()), - Resourcepart.from(resource.toString()) - )); - } catch (XmppStringprepException e) { + return new InternalRepresentation( + JidCreate.entityFullFrom( + Localpart.from(local.toString()), + Domainpart.from(domain.toString()), + Resourcepart.from(resource.toString()))); + } catch (final XmppStringprepException e) { throw new IllegalArgumentException(e); } } - static Jid ofEscaped(CharSequence local, CharSequence domain, CharSequence resource) { + public static Jid ofDomain(final CharSequence domain) { try { - if (resource == null) { - return new WrappedJid( - JidCreate.bareFrom( - Localpart.from(local.toString()), - Domainpart.from(domain.toString()) - ) - ); - } - return new WrappedJid(JidCreate.entityFullFrom( - Localpart.from(local.toString()), - Domainpart.from(domain.toString()), - Resourcepart.from(resource.toString()) - )); - } catch (XmppStringprepException e) { - throw new IllegalArgumentException(e); - } - } - - - static Jid ofDomain(CharSequence domain) { - try { - return new WrappedJid(JidCreate.domainBareFrom(domain)); - } catch (XmppStringprepException e) { + return new InternalRepresentation(JidCreate.domainBareFrom(domain)); + } catch (final XmppStringprepException e) { throw new IllegalArgumentException(e); } } - static Jid ofLocalAndDomain(CharSequence local, CharSequence domain) { + public static Jid ofLocalAndDomain(final CharSequence local, final CharSequence domain) { try { - return new WrappedJid( + return new InternalRepresentation( JidCreate.bareFrom( - Localpart.fromUnescaped(local.toString()), - Domainpart.from(domain.toString()) - ) - ); - } catch (XmppStringprepException e) { + Localpart.from(local.toString()), Domainpart.from(domain.toString()))); + } catch (final XmppStringprepException e) { throw new IllegalArgumentException(e); } } - static Jid ofDomainAndResource(CharSequence domain, CharSequence resource) { + public static Jid ofDomainAndResource(CharSequence domain, CharSequence resource) { try { - return new WrappedJid( + return new InternalRepresentation( JidCreate.domainFullFrom( Domainpart.from(domain.toString()), - Resourcepart.from(resource.toString()) - )); - } catch (XmppStringprepException e) { + Resourcepart.from(resource.toString()))); + } catch (final XmppStringprepException e) { throw new IllegalArgumentException(e); } } - static Jid ofLocalAndDomainEscaped(CharSequence local, CharSequence domain) { + public static Jid of(final CharSequence input) { + if (input instanceof Jid jid) { + return jid; + } try { - return new WrappedJid( - JidCreate.bareFrom( - Localpart.from(local.toString()), - Domainpart.from(domain.toString()) - ) - ); - } catch (XmppStringprepException e) { + return new InternalRepresentation(JidCreate.from(input)); + } catch (final XmppStringprepException e) { throw new IllegalArgumentException(e); } } - static Jid of(CharSequence jid) { - if (jid instanceof Jid) { - return (Jid) jid; - } - Matcher matcher = JID.matcher(jid); - if (matcher.matches()) { - return of(matcher.group(2), matcher.group(3), matcher.group(5)); - } else { - throw new IllegalArgumentException("Could not parse JID: " + jid); + public static Jid ofUserInput(final CharSequence input) { + final var jid = of(input); + if (CharMatcher.is('@').matchesAnyOf(jid.getDomain())) { + throw new IllegalArgumentException("Domain should not contain @"); } + return jid; + } + + public static Jid ofOrInvalid(final String input) { + return ofOrInvalid(input, false); } - static Jid ofEscaped(CharSequence jid) { + /** + * + * @param jid a string representation of the jid to parse + * @param fallback indicates whether an attempt should be made to parse a bare version of the jid + * @return an instance of Jid; may be Jid.Invalid + */ + public static Jid ofOrInvalid(final String jid, final boolean fallback) { try { - return new WrappedJid(JidCreate.from(jid)); - } catch (final XmppStringprepException e) { - throw new IllegalArgumentException(e); + return Jid.of(jid); + } catch (final IllegalArgumentException e) { + return Jid.invalidOf(jid, fallback); } } - boolean isFullJid(); + private static Jid invalidOf(final String jid, boolean fallback) { + final int pos = jid.indexOf('/'); + if (fallback && pos >= 0 && jid.length() >= pos + 1) { + if (jid.substring(pos + 1).trim().isEmpty()) { + return Jid.of(jid.substring(0, pos)); + } + } + return new Invalid(jid); + } + + public abstract boolean isFullJid(); + + public abstract boolean isBareJid(); + + public abstract boolean isDomainJid(); + + public abstract Jid asBareJid(); - boolean isBareJid(); + public abstract Jid withResource(CharSequence resource); - boolean isDomainJid(); + public abstract String getLocal(); - Jid asBareJid(); + public abstract Jid getDomain(); - Jid withResource(CharSequence resource); + public abstract String getResource(); - String getLocal(); + private static class InternalRepresentation extends Jid { + private final org.jxmpp.jid.Jid inner; - String getEscapedLocal(); + private InternalRepresentation(final org.jxmpp.jid.Jid inner) { + this.inner = inner; + } + + @Override + public boolean isFullJid() { + return inner.isEntityFullJid() || inner.isDomainFullJid(); + } - Jid getDomain(); + @Override + public boolean isBareJid() { + return inner.isDomainBareJid() || inner.isEntityBareJid(); + } - String getResource(); + @Override + public boolean isDomainJid() { + return inner.isDomainBareJid() || inner.isDomainFullJid(); + } + + @Override + public Jid asBareJid() { + return new InternalRepresentation(inner.asBareJid()); + } + + @Override + public Jid withResource(CharSequence resource) { + final Localpart localpart = inner.getLocalpartOrNull(); + try { + final Resourcepart resourcepart = Resourcepart.from(resource.toString()); + if (localpart == null) { + return new InternalRepresentation( + JidCreate.domainFullFrom(inner.getDomain(), resourcepart)); + } else { + return new InternalRepresentation( + JidCreate.fullFrom(localpart, inner.getDomain(), resourcepart)); + } + } catch (XmppStringprepException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public String getLocal() { + final Localpart localpart = inner.getLocalpartOrNull(); + return localpart == null ? null : localpart.toString(); + } + + @Override + public Jid getDomain() { + return new InternalRepresentation(inner.asDomainBareJid()); + } + + @Override + public String getResource() { + final Resourcepart resourcepart = inner.getResourceOrNull(); + return resourcepart == null ? null : resourcepart.toString(); + } + + @NonNull + @Override + public String toString() { + return inner.toString(); + } + + @Override + public int length() { + return inner.length(); + } - String toEscapedString(); + @Override + public char charAt(int i) { + return inner.charAt(i); + } + + @NonNull + @Override + public CharSequence subSequence(int i, int i1) { + return inner.subSequence(i, i1); + } + + @Override + public int compareTo(Jid jid) { + if (jid instanceof InternalRepresentation) { + return inner.compareTo(((InternalRepresentation) jid).inner); + } else { + return 0; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + InternalRepresentation that = (InternalRepresentation) o; + return inner.equals(that.inner); + } + + @Override + public int hashCode() { + return inner.hashCode(); + } + } + + public static class Invalid extends Jid { + + private final String value; + + private Invalid(final String jid) { + this.value = jid; + } + + @Override + @NonNull + public String toString() { + return value; + } + + @Override + public boolean isFullJid() { + throw new AssertionError("Not implemented"); + } + + @Override + public boolean isBareJid() { + throw new AssertionError("Not implemented"); + } + + @Override + public boolean isDomainJid() { + throw new AssertionError("Not implemented"); + } + + @Override + public Jid asBareJid() { + throw new AssertionError("Not implemented"); + } + + @Override + public Jid withResource(CharSequence charSequence) { + throw new AssertionError("Not implemented"); + } + + @Override + public String getLocal() { + throw new AssertionError("Not implemented"); + } + + @Override + public Jid getDomain() { + throw new AssertionError("Not implemented"); + } + + @Override + public String getResource() { + throw new AssertionError("Not implemented"); + } + + @Override + public int length() { + return value.length(); + } + + @Override + public char charAt(int index) { + return value.charAt(index); + } + + @NonNull + @Override + public CharSequence subSequence(int start, int end) { + return value.subSequence(start, end); + } + + @Override + public int compareTo(@NonNull Jid o) { + throw new AssertionError("Not implemented"); + } + + public static Jid getNullForInvalid(final Jid jid) { + if (jid instanceof Invalid) { + return null; + } else { + return jid; + } + } + + public static boolean isValid(Jid jid) { + return !(jid instanceof Invalid); + } + + public static boolean hasValidFrom(final Stanza stanza) { + final String from = stanza.getAttribute("from"); + if (from == null) { + return false; + } + try { + Jid.of(from); + return true; + } catch (final IllegalArgumentException e) { + return false; + } + } + } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/WrappedJid.java b/src/main/java/eu/siacs/conversations/xmpp/WrappedJid.java deleted file mode 100644 index 08fb6e6dc..000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/WrappedJid.java +++ /dev/null @@ -1,130 +0,0 @@ -package eu.siacs.conversations.xmpp; - - -import androidx.annotation.NonNull; - -import org.jxmpp.jid.Jid; -import org.jxmpp.jid.impl.JidCreate; -import org.jxmpp.jid.parts.Localpart; -import org.jxmpp.jid.parts.Resourcepart; -import org.jxmpp.stringprep.XmppStringprepException; - - -public class WrappedJid implements eu.siacs.conversations.xmpp.Jid { - private final Jid inner; - - WrappedJid(Jid inner) { - this.inner = inner; - } - - @Override - public boolean isFullJid() { - return inner.isEntityFullJid() || inner.isDomainFullJid(); - } - - @Override - public boolean isBareJid() { - return inner.isDomainBareJid() || inner.isEntityBareJid(); - } - - @Override - public boolean isDomainJid() { - return inner.isDomainBareJid() || inner.isDomainFullJid(); - } - - @Override - public eu.siacs.conversations.xmpp.Jid asBareJid() { - return new WrappedJid(inner.asBareJid()); - } - - @Override - public eu.siacs.conversations.xmpp.Jid withResource(CharSequence resource) { - final Localpart localpart = inner.getLocalpartOrNull(); - try { - final Resourcepart resourcepart = Resourcepart.from(resource.toString()); - if (localpart == null) { - return new WrappedJid(JidCreate.domainFullFrom(inner.getDomain(),resourcepart)); - } else { - return new WrappedJid( - JidCreate.fullFrom( - localpart, - inner.getDomain(), - resourcepart - )); - } - } catch (XmppStringprepException e) { - throw new IllegalArgumentException(e); - } - } - - @Override - public String getLocal() { - final Localpart localpart = inner.getLocalpartOrNull(); - return localpart == null ? null : localpart.asUnescapedString(); - } - - @Override - public String getEscapedLocal() { - final Localpart localpart = inner.getLocalpartOrNull(); - return localpart == null ? null : localpart.toString(); - } - - @Override - public eu.siacs.conversations.xmpp.Jid getDomain() { - return new WrappedJid(inner.asDomainBareJid()); - } - - @Override - public String getResource() { - final Resourcepart resourcepart = inner.getResourceOrNull(); - return resourcepart == null ? null : resourcepart.toString(); - } - - @Override - public String toEscapedString() { - return inner.toString(); - } - - @NonNull - @Override - public String toString() { - return inner.asUnescapedString(); - } - - @Override - public int length() { - return inner.length(); - } - - @Override - public char charAt(int i) { - return inner.charAt(i); - } - - @Override - public CharSequence subSequence(int i, int i1) { - return inner.subSequence(i,i1); - } - - @Override - public int compareTo(eu.siacs.conversations.xmpp.Jid jid) { - if (jid instanceof WrappedJid) { - return inner.compareTo(((WrappedJid) jid).inner); - } else { - return 0; - } - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - WrappedJid that = (WrappedJid) o; - return inner.equals(that.inner); - } - - @Override - public int hashCode() { - return inner.hashCode(); - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 6ce05e378..d02d533ca 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -2319,7 +2319,7 @@ public class XmppConnection implements Runnable { for (final Element element : elements) { if (element.getName().equals("item")) { final Jid jid = - InvalidJid.getNullForInvalid( + Jid.Invalid.getNullForInvalid( element.getAttributeAsJid("jid")); if (jid != null && !jid.equals(account.getDomain())) { items.add(jid); @@ -2484,7 +2484,7 @@ public class XmppConnection implements Runnable { final Tag stream = Tag.start("stream:stream"); stream.setAttribute("to", account.getServer()); if (from) { - stream.setAttribute("from", account.getJid().asBareJid().toEscapedString()); + stream.setAttribute("from", account.getJid().asBareJid().toString()); } stream.setAttribute("version", "1.0"); stream.setAttribute("xml:lang", LocalizedContent.STREAM_LANGUAGE); @@ -2703,7 +2703,7 @@ public class XmppConnection implements Runnable { public List<String> getMucServersWithholdAccount() { final List<String> servers = getMucServers(); - servers.remove(account.getDomain().toEscapedString()); + servers.remove(account.getDomain().toString()); return servers; } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index 1f93ed924..d01880ab8 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -4,9 +4,7 @@ import android.telecom.TelecomManager; import android.telecom.VideoProfile; import android.util.Base64; import android.util.Log; - import androidx.annotation.Nullable; - import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.base.Preconditions; @@ -15,7 +13,6 @@ import com.google.common.cache.CacheBuilder; import com.google.common.collect.Collections2; import com.google.common.collect.ComparisonChain; import com.google.common.collect.ImmutableSet; - import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; @@ -39,10 +36,8 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; import eu.siacs.conversations.xmpp.jingle.transports.InbandBytestreamsTransport; import eu.siacs.conversations.xmpp.jingle.transports.Transport; - import im.conversations.android.xmpp.model.jingle.Jingle; import im.conversations.android.xmpp.model.stanza.Iq; - import java.lang.ref.WeakReference; import java.security.SecureRandom; import java.util.Arrays; @@ -353,7 +348,8 @@ public class JingleConnectionManager extends AbstractConnectionManager { Log.d( Config.LOGTAG, id.account.getJid().asBareJid() - + ": updated previous busy because call got picked up by another device"); + + ": updated previous busy because call got picked up by" + + " another device"); mXmppConnectionService.getNotificationService().clearMissedCall(previousBusy); return; } @@ -393,15 +389,14 @@ public class JingleConnectionManager extends AbstractConnectionManager { final String theirSessionId = id.sessionId; if (ComparisonChain.start() .compare(ourSessionId, theirSessionId) - .compare( - account.getJid().toEscapedString(), - id.with.toEscapedString()) + .compare(account.getJid().toString(), id.with.toString()) .result() > 0) { Log.d( Config.LOGTAG, account.getJid().asBareJid() - + ": our session lost tie break. automatically accepting their session. winning Session=" + + ": our session lost tie break. automatically accepting" + + " their session. winning Session=" + theirSessionId); // TODO a retract for this reason should probably include some indication of // tie break @@ -417,7 +412,8 @@ public class JingleConnectionManager extends AbstractConnectionManager { Log.d( Config.LOGTAG, account.getJid().asBareJid() - + ": our session won tie break. waiting for other party to accept. winningSession=" + + ": our session won tie break. waiting for other party to" + + " accept. winningSession=" + ourSessionId); // TODO reject their session with <tie-break/>? } @@ -453,7 +449,8 @@ public class JingleConnectionManager extends AbstractConnectionManager { Log.d( Config.LOGTAG, id.account.getJid().asBareJid() - + ": ignoring proposal because busy on this device but there are other devices"); + + ": ignoring proposal because busy on this device but" + + " there are other devices"); } } else { final JingleRtpConnection rtpConnection = @@ -772,12 +769,14 @@ public class JingleConnectionManager extends AbstractConnectionManager { if (hasMatchingRtpSession(account, with, media)) { Log.d( Config.LOGTAG, - "ignoring request to propose jingle session because the other party already created one for us"); + "ignoring request to propose jingle session because the other party" + + " already created one for us"); // TODO return something that we can parse the connection of of return null; } throw new IllegalStateException( - "There is already a running RTP session. This should have been caught by the UI"); + "There is already a running RTP session. This should have been caught by" + + " the UI"); } final CallIntegration callIntegration = new CallIntegration(mXmppConnectionService.getApplicationContext()); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index b4f1882f5..10a6d4ba6 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -4,10 +4,8 @@ import android.content.Intent; import android.telecom.TelecomManager; import android.telecom.VideoProfile; import android.util.Log; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.common.base.Joiner; import com.google.common.base.Optional; import com.google.common.base.Preconditions; @@ -24,7 +22,6 @@ 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.BuildConfig; import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.axolotl.AxolotlService; @@ -47,15 +44,8 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.Proceed; import eu.siacs.conversations.xmpp.jingle.stanzas.Propose; import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; - import im.conversations.android.xmpp.model.jingle.Jingle; import im.conversations.android.xmpp.model.stanza.Iq; - -import org.webrtc.EglBase; -import org.webrtc.IceCandidate; -import org.webrtc.PeerConnection; -import org.webrtc.VideoTrack; - import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -67,6 +57,10 @@ import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import org.webrtc.EglBase; +import org.webrtc.IceCandidate; +import org.webrtc.PeerConnection; +import org.webrtc.VideoTrack; public class JingleRtpConnection extends AbstractJingleConnection implements WebRTCWrapper.EventCallback, CallIntegration.Callback, OngoingRtpSession { @@ -278,7 +272,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.w( Config.LOGTAG, id.account.getJid().asBareJid() - + ": PeerConnection was not initialized when processing transport info. this usually indicates a race condition that can be ignored"); + + ": PeerConnection was not initialized when processing transport info." + + " this usually indicates a race condition that can be ignored"); } } @@ -625,7 +620,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.d( Config.LOGTAG, id.getAccount().getJid().asBareJid() - + ": unable to rollback local description after receiving content-reject", + + ": unable to rollback local description after receiving" + + " content-reject", cause); webRTCWrapper.close(); sendSessionTerminate(Reason.FAILED_APPLICATION, cause.getMessage()); @@ -694,7 +690,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.d( Config.LOGTAG, id.getAccount().getJid().asBareJid() - + ": unable to rollback local description after trying to retract content-add", + + ": unable to rollback local description after trying to retract" + + " content-add", cause); webRTCWrapper.close(); sendSessionTerminate(Reason.FAILED_APPLICATION, cause.getMessage()); @@ -773,14 +770,16 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.d( Config.LOGTAG, id.account.getJid().asBareJid() - + ": remote has accepted our upgrade to senders=both"); + + ": remote has accepted our upgrade to" + + " senders=both"); acceptContentAdd( ContentAddition.summary(modifiedSenders), modifiedSenders); } else { Log.d( Config.LOGTAG, id.account.getJid().asBareJid() - + ": remote has rejected our upgrade to senders=both"); + + ": remote has rejected our upgrade to" + + " senders=both"); acceptContentAdd(contentAddition, incomingContentAdd); } }); @@ -1072,7 +1071,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.w( Config.LOGTAG, id.account.getJid().asBareJid() - + ": no identification tags found in initial offer. we won't be able to calculate mLineIndices"); + + ": no identification tags found in initial offer. we won't be able to" + + " calculate mLineIndices"); } return identificationTags; } @@ -1169,7 +1169,8 @@ public class JingleRtpConnection extends AbstractJingleConnection sendSessionTerminate( Reason.SECURITY_ERROR, String.format( - "Your session proposal (Jingle Message Initiation) included media %s but your session-initiate was %s", + "Your session proposal (Jingle Message Initiation) included media" + + " %s but your session-initiate was %s", this.proposedMedia, contentMap.getMedia())); return; } @@ -1254,7 +1255,8 @@ public class JingleRtpConnection extends AbstractJingleConnection sendSessionTerminate( Reason.SECURITY_ERROR, String.format( - "Your session-included included media %s but our session-initiate was %s", + "Your session-included included media %s but our session-initiate was" + + " %s", this.proposedMedia, contentMap.getMedia())); return; } @@ -1342,7 +1344,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.w( Config.LOGTAG, id.account.getJid().asBareJid() - + ": ICE servers got discovered when session was already terminated. nothing to do."); + + ": ICE servers got discovered when session was already terminated." + + " nothing to do."); return; } final boolean includeCandidates = remoteHasSdpOfferAnswer(); @@ -1445,7 +1448,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.w( Config.LOGTAG, id.account.getJid().asBareJid() - + ": preparing session accept was too slow. already terminated. nothing to do."); + + ": preparing session accept was too slow. already terminated. nothing" + + " to do."); return; } transitionOrThrow(State.SESSION_ACCEPTED); @@ -1488,10 +1492,10 @@ public class JingleRtpConnection extends AbstractJingleConnection + ": delivered message to JingleRtpConnection " + message); switch (message.getName()) { - case "propose" -> receivePropose( - from, Propose.upgrade(message), serverMessageId, timestamp); - case "proceed" -> receiveProceed( - from, Proceed.upgrade(message), serverMessageId, timestamp); + case "propose" -> + receivePropose(from, Propose.upgrade(message), serverMessageId, timestamp); + case "proceed" -> + receiveProceed(from, Proceed.upgrade(message), serverMessageId, timestamp); case "retract" -> receiveRetract(from, serverMessageId, timestamp); case "reject" -> receiveReject(from, serverMessageId, timestamp); case "accept" -> receiveAccept(from, serverMessageId, timestamp); @@ -1605,7 +1609,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.d( Config.LOGTAG, id.account.getJid() - + ": received reject while in SESSION_INITIATED_PRE_APPROVED. callee reconsidered before receiving session-init"); + + ": received reject while in SESSION_INITIATED_PRE_APPROVED. callee" + + " reconsidered before receiving session-init"); closeTransitionLogFinish(State.TERMINATED_DECLINED_OR_BUSY); return; } @@ -1727,7 +1732,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.d( Config.LOGTAG, id.account.getJid().asBareJid() - + ": remote party signaled support for OMEMO verification but we have OMEMO disabled"); + + ": remote party signaled support for OMEMO" + + " verification but we have OMEMO disabled"); } this.omemoVerification.setDeviceId(null); } @@ -1822,7 +1828,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.w( Config.LOGTAG, id.account.getJid().asBareJid() - + ": ICE servers got discovered when session was already terminated. nothing to do."); + + ": ICE servers got discovered when session was already terminated." + + " nothing to do."); return; } final boolean includeCandidates = remoteHasSdpOfferAnswer(); @@ -1917,7 +1924,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.w( Config.LOGTAG, id.account.getJid().asBareJid() - + ": preparing session was too slow. already terminated. nothing to do."); + + ": preparing session was too slow. already terminated. nothing to" + + " do."); return; } this.transitionOrThrow(targetState); @@ -1956,7 +1964,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.w( Config.LOGTAG, id.account.getJid().asBareJid() - + ": unable to use OMEMO DTLS verification on outgoing session initiate. falling back", + + ": unable to use OMEMO DTLS verification on outgoing" + + " session initiate. falling back", e); return rtpContentMap; }, @@ -2070,9 +2079,10 @@ public class JingleRtpConnection extends AbstractJingleConnection case CONNECTED -> RtpEndUserState.CONNECTED; case NEW, CONNECTING -> RtpEndUserState.CONNECTING; case CLOSED -> RtpEndUserState.ENDING_CALL; - default -> zeroDuration() - ? RtpEndUserState.CONNECTIVITY_ERROR - : RtpEndUserState.RECONNECTING; + default -> + zeroDuration() + ? RtpEndUserState.CONNECTIVITY_ERROR + : RtpEndUserState.RECONNECTING; }; } @@ -2116,13 +2126,14 @@ public class JingleRtpConnection extends AbstractJingleConnection } case TERMINATED_SUCCESS -> this.callIntegration.success(); case ACCEPTED -> this.callIntegration.accepted(); - case RETRACTED, RETRACTED_RACED, TERMINATED_CANCEL_OR_TIMEOUT -> this.callIntegration - .retracted(); + case RETRACTED, RETRACTED_RACED, TERMINATED_CANCEL_OR_TIMEOUT -> + this.callIntegration.retracted(); case TERMINATED_CONNECTIVITY_ERROR, - TERMINATED_APPLICATION_FAILURE, - TERMINATED_SECURITY_ERROR -> this.callIntegration.error(); - default -> throw new IllegalStateException( - String.format("%s is not handled", this.state)); + TERMINATED_APPLICATION_FAILURE, + TERMINATED_SECURITY_ERROR -> + this.callIntegration.error(); + default -> + throw new IllegalStateException(String.format("%s is not handled", this.state)); } } @@ -2195,14 +2206,18 @@ public class JingleRtpConnection extends AbstractJingleConnection cancelRingingTimeout(); acceptCallFromSessionInitialized(); } - case ACCEPTED -> Log.w( - Config.LOGTAG, - id.account.getJid().asBareJid() - + ": the call has already been accepted with another client. UI was just lagging behind"); - case PROCEED, SESSION_ACCEPTED -> Log.w( - Config.LOGTAG, - id.account.getJid().asBareJid() - + ": the call has already been accepted. user probably double tapped the UI"); + case ACCEPTED -> + Log.w( + Config.LOGTAG, + id.account.getJid().asBareJid() + + ": the call has already been accepted with another client." + + " UI was just lagging behind"); + case PROCEED, SESSION_ACCEPTED -> + Log.w( + Config.LOGTAG, + id.account.getJid().asBareJid() + + ": the call has already been accepted. user probably double" + + " tapped the UI"); default -> throw new IllegalStateException("Can not accept call from " + this.state); } } @@ -2212,7 +2227,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.w( Config.LOGTAG, id.account.getJid().asBareJid() - + ": received rejectCall() when session has already been terminated. nothing to do"); + + ": received rejectCall() when session has already been terminated." + + " nothing to do"); return; } switch (this.state) { @@ -2245,7 +2261,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.w( Config.LOGTAG, id.account.getJid().asBareJid() - + ": received endCall() when session has already been terminated. nothing to do"); + + ": received endCall() when session has already been terminated." + + " nothing to do"); return; } if (isInState(State.PROPOSED) && isResponder()) { @@ -2447,7 +2464,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.d( Config.LOGTAG, id.account.getJid().asBareJid() - + ": not sending session-terminate after connectivity error because session is already in state " + + ": not sending session-terminate after connectivity error" + + " because session is already in state " + this.state); return; } @@ -2649,7 +2667,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.d( Config.LOGTAG, id.account.getJid().asBareJid() - + ": no need to send session-terminate after failed connection. Other party already did"); + + ": no need to send session-terminate after failed connection." + + " Other party already did"); return; } sendSessionTerminate(Reason.CONNECTIVITY_ERROR); @@ -2704,7 +2723,8 @@ public class JingleRtpConnection extends AbstractJingleConnection // callback when the rtp session has already ended. Log.w( Config.LOGTAG, - "CallIntegration requested incoming call UI but session was already terminated"); + "CallIntegration requested incoming call UI but session was already" + + " terminated"); return; } // TODO apparently this can be called too early as well? @@ -2736,8 +2756,8 @@ public class JingleRtpConnection extends AbstractJingleConnection // we need to start the UI to a) show it and b) be able to ask for permissions final Intent intent = new Intent(xmppConnectionService, RtpSessionActivity.class); intent.setAction(RtpSessionActivity.ACTION_ACCEPT_CALL); - intent.putExtra(RtpSessionActivity.EXTRA_ACCOUNT, id.account.getJid().toEscapedString()); - intent.putExtra(RtpSessionActivity.EXTRA_WITH, id.with.toEscapedString()); + intent.putExtra(RtpSessionActivity.EXTRA_ACCOUNT, id.account.getJid().toString()); + intent.putExtra(RtpSessionActivity.EXTRA_WITH, id.with.toString()); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, id.sessionId); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java index 2925592ea..8cebf080f 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java @@ -1,10 +1,8 @@ package eu.siacs.conversations.xmpp.jingle.transports; import android.util.Log; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.common.base.Joiner; import com.google.common.base.MoreObjects; import com.google.common.base.Optional; @@ -22,7 +20,6 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.SettableFuture; - import eu.siacs.conversations.Config; import eu.siacs.conversations.utils.SocksSocketFactory; import eu.siacs.conversations.xml.Element; @@ -33,7 +30,6 @@ import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection; import eu.siacs.conversations.xmpp.jingle.DirectConnectionUtils; import eu.siacs.conversations.xmpp.jingle.stanzas.SocksByteStreamsTransportInfo; import im.conversations.android.xmpp.model.stanza.Iq; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -104,8 +100,8 @@ public class SocksByteStreamsTransport implements Transport { .join( Arrays.asList( streamId, - id.with.toEscapedString(), - id.account.getJid().toEscapedString())), + id.with.toString(), + id.account.getJid().toString())), StandardCharsets.UTF_8) .toString(); final var ourDestination = @@ -115,8 +111,8 @@ public class SocksByteStreamsTransport implements Transport { .join( Arrays.asList( streamId, - id.account.getJid().toEscapedString(), - id.with.toEscapedString())), + id.account.getJid().toString(), + id.with.toString())), StandardCharsets.UTF_8) .toString(); @@ -255,7 +251,7 @@ public class SocksByteStreamsTransport implements Transport { final Element query = proxyActivation.addChild("query", Namespace.BYTE_STREAMS); query.setAttribute("sid", this.streamId); final Element activate = query.addChild("activate"); - activate.setContent(id.with.toEscapedString()); + activate.setContent(id.with.toString()); xmppConnection.sendIqPacket( proxyActivation, (response) -> { @@ -731,10 +727,12 @@ public class SocksByteStreamsTransport implements Transport { && selectedByThemCandidatePriority > candidate.priority) { Log.d( Config.LOGTAG, - "The candidate selected by peer had a higher priority then anything we could try"); + "The candidate selected by peer had a higher priority then anything we" + + " could try"); connectionFuture.setException( new CandidateErrorException( - "The candidate selected by peer had a higher priority then anything we could try")); + "The candidate selected by peer had a higher priority then" + + " anything we could try")); return; } try { @@ -864,7 +862,7 @@ public class SocksByteStreamsTransport implements Transport { return new Candidate( cid, host, - Jid.ofEscaped(jid), + Jid.of(jid), Integer.parseInt(port), Integer.parseInt(priority), CandidateType.valueOf(type.toUpperCase(Locale.ROOT))); diff --git a/src/main/java/im/conversations/android/xmpp/model/bind/Bind.java b/src/main/java/im/conversations/android/xmpp/model/bind/Bind.java index 27264f754..6a7c8605c 100644 --- a/src/main/java/im/conversations/android/xmpp/model/bind/Bind.java +++ b/src/main/java/im/conversations/android/xmpp/model/bind/Bind.java @@ -1,7 +1,6 @@ package im.conversations.android.xmpp.model.bind; import com.google.common.base.Strings; - import im.conversations.android.annotation.XmlElement; import im.conversations.android.xmpp.model.Extension; @@ -26,7 +25,7 @@ public class Bind extends Extension { return null; } try { - return eu.siacs.conversations.xmpp.Jid.ofEscaped(content); + return eu.siacs.conversations.xmpp.Jid.of(content); } catch (final IllegalArgumentException e) { return null; } diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/AuthorizationIdentifier.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/AuthorizationIdentifier.java index e29ae7dea..d477131b8 100644 --- a/src/main/java/im/conversations/android/xmpp/model/sasl2/AuthorizationIdentifier.java +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/AuthorizationIdentifier.java @@ -1,7 +1,6 @@ package im.conversations.android.xmpp.model.sasl2; import com.google.common.base.Strings; - import eu.siacs.conversations.xmpp.Jid; import im.conversations.android.annotation.XmlElement; import im.conversations.android.xmpp.model.Extension; @@ -9,18 +8,17 @@ import im.conversations.android.xmpp.model.Extension; @XmlElement public class AuthorizationIdentifier extends Extension { - public AuthorizationIdentifier() { super(AuthorizationIdentifier.class); } public Jid get() { final var content = getContent(); - if ( Strings.isNullOrEmpty(content)) { + if (Strings.isNullOrEmpty(content)) { return null; } try { - return Jid.ofEscaped(content); + return Jid.of(content); } catch (final IllegalArgumentException e) { return null; } diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java b/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java index 82a8ce3df..3abf016ff 100644 --- a/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java +++ b/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java @@ -1,10 +1,7 @@ package im.conversations.android.xmpp.model.stanza; import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; - -import im.conversations.android.xmpp.model.Extension; import im.conversations.android.xmpp.model.StreamElement; import im.conversations.android.xmpp.model.error.Error; @@ -45,7 +42,7 @@ public abstract class Stanza extends StreamElement { public boolean isInvalid() { final var to = getTo(); final var from = getFrom(); - if (to instanceof InvalidJid || from instanceof InvalidJid) { + if (to instanceof Jid.Invalid || from instanceof Jid.Invalid) { return true; } return false; -- GitLab