Merge commit 'dab3b6052571714a44c292e02e7847305a8c6bc0'

Stephen Paul Weber created

* commit 'dab3b6052571714a44c292e02e7847305a8c6bc0': (39 commits)
  version bump to 2.17.10
  Translated using Weblate (Estonian)
  Translated using Weblate (Japanese)
  Translated using Weblate (Russian)
  Translated using Weblate (Portuguese (Brazil))
  Translated using Weblate (Portuguese (Brazil))
  Translated using Weblate (Spanish)
  Translated using Weblate (Albanian)
  Translated using Weblate (Polish)
  Translated using Weblate (German)
  Translated using Weblate (Chinese (Simplified Han script))
  add changelog for 2.17.10
  Translated using Weblate (Finnish)
  Translated using Weblate (Russian)
  Translated using Weblate (Polish)
  allow audio recording to be pause by tapping the timer
  add media preview icon for slide show
  parse reactions from MUC PMS
  add media icon for spreadsheets
  add occupant id to outgoing MUC pm
  ...

Change summary

CHANGELOG.md                                                                               |   12 
fastlane/metadata/android/de-DE/changelogs/4213104.txt                                     |    2 
fastlane/metadata/android/de-DE/changelogs/4213204.txt                                     |    4 
fastlane/metadata/android/en-US/changelogs/4213104.txt                                     |    2 
fastlane/metadata/android/en-US/changelogs/4213204.txt                                     |    4 
fastlane/metadata/android/es-ES/changelogs/4213104.txt                                     |    2 
fastlane/metadata/android/et/changelogs/42043.txt                                          |    1 
fastlane/metadata/android/et/changelogs/42044.txt                                          |    3 
fastlane/metadata/android/et/changelogs/42046.txt                                          |    1 
fastlane/metadata/android/et/changelogs/42047.txt                                          |    1 
fastlane/metadata/android/et/changelogs/42050.txt                                          |    1 
fastlane/metadata/android/et/changelogs/42059.txt                                          |    2 
fastlane/metadata/android/et/changelogs/42060.txt                                          |    1 
fastlane/metadata/android/et/changelogs/4213104.txt                                        |    2 
fastlane/metadata/android/et/changelogs/4213204.txt                                        |    4 
fastlane/metadata/android/gl-ES/changelogs/4213104.txt                                     |    2 
fastlane/metadata/android/pl-PL/changelogs/4213104.txt                                     |    2 
fastlane/metadata/android/pl-PL/changelogs/4213204.txt                                     |    4 
fastlane/metadata/android/ru-RU/changelogs/4213104.txt                                     |    2 
fastlane/metadata/android/ru-RU/changelogs/4213204.txt                                     |    4 
fastlane/metadata/android/sq/changelogs/4213204.txt                                        |    4 
fastlane/metadata/android/uk/changelogs/4213104.txt                                        |    2 
fastlane/metadata/android/zh-CN/changelogs/4213104.txt                                     |    2 
fastlane/metadata/android/zh-CN/changelogs/4213204.txt                                     |    4 
src/cheogram/java/com/cheogram/ExportBackupService.java                                    |    4 
src/cheogram/java/com/cheogram/android/FinishOnboarding.java                               |    2 
src/cheogram/java/com/cheogram/android/WebxdcPage.java                                     |    2 
src/cheogram/java/com/cheogram/android/WebxdcUpdate.java                                   |    4 
src/cheogram/java/eu/siacs/conversations/entities/AccountConfiguration.java                |    2 
src/cheogram/java/eu/siacs/conversations/services/ImportBackupService.java                 |    2 
src/cheogram/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java              |    4 
src/cheogram/java/eu/siacs/conversations/ui/MagicCreateActivity.java                       |   14 
src/cheogram/java/eu/siacs/conversations/ui/ManageAccountActivity.java                     |    8 
src/cheogram/java/eu/siacs/conversations/ui/WelcomeActivity.java                           |    6 
src/cheogram/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java                 |    4 
src/cheogram/java/eu/siacs/conversations/utils/ProvisioningUtils.java                      |    4 
src/cheogram/java/eu/siacs/conversations/utils/SignupUtils.java                            |    6 
src/conversations/java/eu/siacs/conversations/entities/AccountConfiguration.java           |   20 
src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java            |   27 
src/conversations/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java         |    7 
src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java                  |   18 
src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java                |   18 
src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java                      |   18 
src/conversations/java/eu/siacs/conversations/utils/ProvisioningUtils.java                 |   12 
src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java                       |   16 
src/conversations/res/values-be/strings.xml                                                |    2 
src/main/java/eu/siacs/conversations/crypto/sasl/DowngradeProtection.java                  |    6 
src/main/java/eu/siacs/conversations/crypto/sasl/External.java                             |    6 
src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java                       |    8 
src/main/java/eu/siacs/conversations/entities/Account.java                                 |   22 
src/main/java/eu/siacs/conversations/entities/Bookmark.java                                |  358 
src/main/java/eu/siacs/conversations/entities/Contact.java                                 |   64 
src/main/java/eu/siacs/conversations/entities/Conversation.java                            |    9 
src/main/java/eu/siacs/conversations/entities/Message.java                                 |   39 
src/main/java/eu/siacs/conversations/entities/MucOptions.java                              |  112 
src/main/java/eu/siacs/conversations/entities/RawBlockable.java                            |   17 
src/main/java/eu/siacs/conversations/entities/Reaction.java                                |    6 
src/main/java/eu/siacs/conversations/entities/Room.java                                    |   16 
src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java                      |    5 
src/main/java/eu/siacs/conversations/generator/IqGenerator.java                            |   70 
src/main/java/eu/siacs/conversations/parser/AbstractParser.java                            |  346 
src/main/java/eu/siacs/conversations/parser/IqParser.java                                  |  174 
src/main/java/eu/siacs/conversations/parser/MessageParser.java                             |  547 
src/main/java/eu/siacs/conversations/parser/PresenceParser.java                            |   86 
src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java                      |   31 
src/main/java/eu/siacs/conversations/persistance/UnifiedPushDatabase.java                  |   39 
src/main/java/eu/siacs/conversations/services/AvatarService.java                           |   22 
src/main/java/eu/siacs/conversations/services/CallIntegration.java                         |    2 
src/main/java/eu/siacs/conversations/services/CallIntegrationConnectionService.java        |   22 
src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java                 |   33 
src/main/java/eu/siacs/conversations/services/NotificationService.java                     |   14 
src/main/java/eu/siacs/conversations/services/ShortcutService.java                         |   27 
src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java                       |   69 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java                   |   47 
src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java                            |  159 
src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java                             |  184 
src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java                      |    2 
src/main/java/eu/siacs/conversations/ui/ChooseAccountForProfilePictureActivity.java        |   25 
src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java                         |   77 
src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java                     |   15 
src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java                        |   14 
src/main/java/eu/siacs/conversations/ui/ConversationFragment.java                          |    9 
src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java                 | 1015 
src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java                     |  169 
src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java                           |   45 
src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java                                |   18 
src/main/java/eu/siacs/conversations/ui/MediaBrowserActivity.java                          |   29 
src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java                 |    6 
src/main/java/eu/siacs/conversations/ui/RecordingActivity.java                             |  121 
src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java                            |   57 
src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java                             |   28 
src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java                     |   56 
src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java                             |  928 
src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java                            |   21 
src/main/java/eu/siacs/conversations/ui/XmppActivity.java                                  |   10 
src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java                        |   45 
src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java                     |  103 
src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java                          |   22 
src/main/java/eu/siacs/conversations/ui/fragment/settings/UpSettingsFragment.java          |   48 
src/main/java/eu/siacs/conversations/ui/service/AudioPlayer.java                           |   45 
src/main/java/eu/siacs/conversations/utils/AccountUtils.java                               |   18 
src/main/java/eu/siacs/conversations/utils/BackupFileHeader.java                           |   38 
src/main/java/eu/siacs/conversations/utils/CryptoHelper.java                               |   83 
src/main/java/eu/siacs/conversations/utils/IrregularUnicodeDetector.java                   |  414 
src/main/java/eu/siacs/conversations/utils/JidHelper.java                                  |   18 
src/main/java/eu/siacs/conversations/utils/MimeUtils.java                                  |   70 
src/main/java/eu/siacs/conversations/utils/XmppUri.java                                    |   44 
src/main/java/eu/siacs/conversations/worker/ExportBackupWorker.java                        |    9 
src/main/java/eu/siacs/conversations/xml/Element.java                                      |   12 
src/main/java/eu/siacs/conversations/xmpp/InvalidJid.java                                  |  155 
src/main/java/eu/siacs/conversations/xmpp/Jid.java                                         |  353 
src/main/java/eu/siacs/conversations/xmpp/WrappedJid.java                                  |  130 
src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java                              |    6 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java              |   27 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java                  |  119 
src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java |   22 
src/main/java/im/conversations/android/xmpp/model/Extension.java                           |   11 
src/main/java/im/conversations/android/xmpp/model/bind/Bind.java                           |    3 
src/main/java/im/conversations/android/xmpp/model/sasl2/AuthorizationIdentifier.java       |    6 
src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java                       |    5 
src/main/res/drawable/ic_description_48dp.xml                                              |   15 
src/main/res/drawable/ic_slideshow_48dp.xml                                                |   12 
src/main/res/drawable/ic_table_48dp.xml                                                    |   10 
src/main/res/layout/item_message_content.xml                                               |   12 
src/main/res/values-es/strings.xml                                                         |    8 
src/main/res/values-fi/strings.xml                                                         |    4 
src/main/res/values-ja/strings.xml                                                         |    3 
src/main/res/values-pt-rBR/strings.xml                                                     |   30 
src/playstore/java/eu/siacs/conversations/services/PushManagementService.java              |   11 
src/quicksy/java/eu/siacs/conversations/entities/Entry.java                                |   35 
src/quicksy/java/eu/siacs/conversations/ui/EnterNameActivity.java                          |    9 
src/quicksy/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java                  |   38 
src/quicksy/res/values-be/strings.xml                                                      |    2 
133 files changed, 4,086 insertions(+), 3,362 deletions(-)

Detailed changes

CHANGELOG.md 🔗

@@ -1,5 +1,17 @@
 # Changelog
 
+### Version 2.17.10
+
+* Allow audio recording to be pause by tapping the timer
+* Fix reactions in MUC PMs 
+* Stop accepting 'fallback messages' for reactions, receipts and display markers
+* Add some more media preview icons
+
+### Version 2.17.9
+
+* Make use of SASL SCRAM Downgrade Protection (XEP-0474)
+* Send reactions to MUC PMs to correct JID
+
 ### Version 2.17.8
 
 * Fix some minor UI bugs

fastlane/metadata/android/de-DE/changelogs/4213204.txt 🔗

@@ -0,0 +1,4 @@
+* Audioaufnahme kann durch Antippen des Timers angehalten werden
+* Reaktionen in MUC PMs korrigiert
+* Annahme von 'Fallback-Nachrichten' für Reaktionen, Bestätigungen und Anzeigemarkierungen beendet
+* Weitere Medienvorschau-Symbole hinzugefügt

fastlane/metadata/android/et/changelogs/4213204.txt 🔗

@@ -0,0 +1,4 @@
+* Nüüd saab heliklipi salvestust peatada taimeril klõpsides
+* Parandasime reageerimise jututubade otsesõnumitele
+* Enam me ei võta vastu reageerimiste, lugemisteatiste ja lugemismarkerite asendussõnumeid
+* Lisasime veel mõned meedia eelvaate ikoonid

fastlane/metadata/android/pl-PL/changelogs/4213204.txt 🔗

@@ -0,0 +1,4 @@
+* Umożliwienie wstrzymania nagrywania dźwięku poprzez kliknięcie czasomierza
+* Naprawienie reakcji w prywatnych wiadomościach w rozmowie grupowej
+* Koniec akceptowania „zastępczych wiadomości”, dla reakcji, potwierdzeń i wskaźników przeczytania
+* Dodanie paru więcej ikonek załączników

fastlane/metadata/android/ru-RU/changelogs/4213204.txt 🔗

@@ -0,0 +1,4 @@
+* Добавлена возможность приостановить запись звука нажатием на таймер
+* Исправлены реакции в MUC PM
+* Прекращён приём "резервных сообщений" для реакций, уведомлений о доставке и маркеров отображения
+* Добавлено ещё несколько значков для просмотра медиа

fastlane/metadata/android/sq/changelogs/4213204.txt 🔗

@@ -0,0 +1,4 @@
+* Lejim ndaljeje regjistrimi audio duke shtypur matësin e kohës
+* Ndreqje reagimesh në MP-ra MUC 
+* Reshtje pranimi 'fallback messages' për reagime, dëftesa dhe shenja në ekrani
+* Shtim i disa ikonash paraparjeje media më tepër

src/cheogram/java/com/cheogram/ExportBackupService.java 🔗

@@ -308,7 +308,7 @@ public class ExportBackupService extends Worker {
             }
             Log.d(Config.LOGTAG, String.format("exporting data for account %s (%s)", account.getJid().asBareJid(), account.getUuid()));
             final Progress progress = new Progress(notification, max, count);
-            final File file = new File(FileBackend.getBackupDirectory(context), account.getJid().asBareJid().toEscapedString() + ".xml.pgp");
+            final File file = new File(FileBackend.getBackupDirectory(context), account.getJid().asBareJid().toString() + ".xml.pgp");
             files.add(file);
             final File directory = file.getParentFile();
             if (directory != null && directory.mkdirs()) {
@@ -333,7 +333,7 @@ public class ExportBackupService extends Worker {
                     new byte[4096]
                 ),
                 PGPLiteralDataGenerator.UTF8,
-                account.getJid().asBareJid().toEscapedString() + ".xml",
+                account.getJid().asBareJid().toString() + ".xml",
                 PGPLiteralDataGenerator.NOW,
                 new byte[4096]
             ));

src/cheogram/java/com/cheogram/android/FinishOnboarding.java 🔗

@@ -56,7 +56,7 @@ public class FinishOnboarding {
 				return;
 			}
 
-			dataForm.put("new-jid", newAccount.getJid().toEscapedString());
+			dataForm.put("new-jid", newAccount.getJid().toString());
 			dataForm.submit();
 			command.setAttribute("action", "execute");
 			iq.setTo(iq.getFrom());

src/cheogram/java/com/cheogram/android/WebxdcPage.java 🔗

@@ -395,7 +395,7 @@ public class WebxdcPage implements ConversationPage {
 				final var occupantId = conversation.getMucOptions().getSelf().getOccupantId();
 				if (occupantId != null) return occupantId;
 			}
-			return "xmpp:" + Uri.encode(selfJid().toEscapedString(), "@/+");
+			return "xmpp:" + Uri.encode(selfJid().toString(), "@/+");
 		}
 
 		@JavascriptInterface

src/cheogram/java/com/cheogram/android/WebxdcUpdate.java 🔗

@@ -63,7 +63,7 @@ public class WebxdcUpdate {
 		ContentValues cv = new ContentValues();
 		cv.put(Message.CONVERSATION, conversationId);
 		cv.put("message_id", messageId);
-		cv.put("sender", sender.toEscapedString());
+		cv.put("sender", sender.toString());
 		cv.put("thread", thread);
 		cv.put("threadParent", threadParent);
 		if (info != null) cv.put("info", info);
@@ -75,7 +75,7 @@ public class WebxdcUpdate {
 
 	public String toString() {
 		StringBuilder body = new StringBuilder("{\"sender\":");
-		body.append(JSONObject.quote(sender.toEscapedString()));
+		body.append(JSONObject.quote(sender.toString()));
 		if (serial != null) {
 			body.append(",\"serial\":");
 			body.append(serial.toString());

src/cheogram/java/eu/siacs/conversations/services/ImportBackupService.java 🔗

@@ -277,7 +277,7 @@ public class ImportBackupService extends Service {
             db.setTransactionSuccessful();
             db.endTransaction();
             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()});
+            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.getLocal(), jid.getDomain().toString()});
             countCursor.moveToFirst();
             final int count = countCursor.getInt(0);
             Log.d(Config.LOGTAG, String.format("restored %d messages in %s", count, stopwatch.stop().toString()));

src/cheogram/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java 🔗

@@ -133,7 +133,7 @@ public class EasyOnboardingInviteActivity extends XmppActivity implements EasyOn
         }
         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;
         }
@@ -143,7 +143,7 @@ public class EasyOnboardingInviteActivity extends XmppActivity implements EasyOn
 
     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);
     }
 

src/cheogram/java/eu/siacs/conversations/ui/MagicCreateActivity.java 🔗

@@ -70,15 +70,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())) {
                     binding.username.setError(getString(R.string.invalid_username));
                     binding.username.requestFocus();
                 } else {
@@ -140,11 +140,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()));
             } catch (IllegalArgumentException e) {
                 binding.fullJid.setVisibility(View.INVISIBLE);
             }

src/cheogram/java/eu/siacs/conversations/ui/ManageAccountActivity.java 🔗

@@ -119,7 +119,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
             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;
                 }
@@ -136,7 +136,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
     @Override
     public void onSaveInstanceState(final Bundle savedInstanceState) {
         if (selectedAccount != null) {
-            savedInstanceState.putString(STATE_SELECTED_ACCOUNT, selectedAccount.getJid().asBareJid().toEscapedString());
+            savedInstanceState.putString(STATE_SELECTED_ACCOUNT, selectedAccount.getJid().asBareJid().toString());
         }
         super.onSaveInstanceState(savedInstanceState);
     }
@@ -156,7 +156,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
             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
@@ -347,7 +347,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
     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);
     }
 

src/cheogram/java/eu/siacs/conversations/ui/WelcomeActivity.java 🔗

@@ -97,7 +97,7 @@ public class WelcomeActivity extends XmppActivity implements XmppConnectionServi
 
         Intent intent = new Intent(this, StartConversationActivity.class);
         intent.putExtra("init", true);
-        intent.putExtra(EXTRA_ACCOUNT, onboardingAccount.getJid().asBareJid().toEscapedString());
+        intent.putExtra(EXTRA_ACCOUNT, onboardingAccount.getJid().asBareJid().toString());
         onboardingAccount = null;
         startActivity(intent);
         finish();
@@ -172,7 +172,7 @@ public class WelcomeActivity extends XmppActivity implements XmppConnectionServi
             } else {
                 binding.registerNewAccount.setText("Working...");
                 binding.registerNewAccount.setEnabled(false);
-                onboardingAccount = new Account(Jid.ofLocalAndDomain(UUID.randomUUID().toString(), Config.ONBOARDING_DOMAIN.toEscapedString()), CryptoHelper.createPassword(new SecureRandom()));
+                onboardingAccount = new Account(Jid.ofLocalAndDomain(UUID.randomUUID().toString(), Config.ONBOARDING_DOMAIN.toString()), CryptoHelper.createPassword(new SecureRandom()));
                 onboardingAccount.setOption(Account.OPTION_REGISTER, true);
                 onboardingAccount.setOption(Account.OPTION_FIXED_USERNAME, true);
                 xmppConnectionService.createAccount(onboardingAccount);
@@ -259,7 +259,7 @@ public class WelcomeActivity extends XmppActivity implements XmppConnectionServi
     @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);

src/cheogram/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java 🔗

@@ -26,12 +26,12 @@ public class PhoneNumberUtilWrapper {
         try {
             return getInstance(context).format(toPhoneNumber(context, jid), PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL).replace(' ','\u202F');
         } catch (Exception e) {
-            return jid.getEscapedLocal();
+            return jid.getLocal();
         }
     }
 
     public static Phonenumber.PhoneNumber toPhoneNumber(Context context, Jid jid) throws NumberParseException {
-        return getInstance(context).parse(jid.getEscapedLocal(), "de");
+        return getInstance(context).parse(jid.getLocal(), "de");
     }
 
     public static String normalize(Context context, String input) throws IllegalArgumentException, NumberParseException {

src/cheogram/java/eu/siacs/conversations/utils/ProvisioningUtils.java 🔗

@@ -31,11 +31,11 @@ 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);
     }

src/cheogram/java/eu/siacs/conversations/utils/SignupUtils.java 🔗

@@ -24,10 +24,10 @@ public class SignupUtils {
     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;

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,
     }
-
 }
-

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(

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

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

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

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

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

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;
     }
-}
+}

src/main/java/eu/siacs/conversations/crypto/sasl/DowngradeProtection.java 🔗

@@ -9,8 +9,8 @@ import java.util.Collection;
 
 public class DowngradeProtection {
 
-    private static final char SEPARATOR = ',';
-    private static final char SEPARATOR_MECHANISM_AND_BINDING = '|';
+    private static final char SEPARATOR = 0x1E;
+    private static final char SEPARATOR_MECHANISM_AND_BINDING = 0x1F;
 
     public final ImmutableList<String> mechanisms;
     public final ImmutableList<String> channelBindings;
@@ -26,7 +26,7 @@ public class DowngradeProtection {
         this.channelBindings = null;
     }
 
-    public String asDString() {
+    public String asHString() {
         ensureSaslMechanismFormat(this.mechanisms);
         ensureNoSeparators(this.mechanisms);
         if (this.channelBindings != null) {

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

src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java 🔗

@@ -183,7 +183,7 @@ public abstract class ScramMechanism extends SaslMechanism {
         final String i = attributes.get("i");
         final String s = attributes.get("s");
         final String nonce = attributes.get("r");
-        final String d = attributes.get("d");
+        final String h = attributes.get("h");
         if (Strings.isNullOrEmpty(s) || Strings.isNullOrEmpty(nonce) || Strings.isNullOrEmpty(i)) {
             throw new AuthenticationException("Missing attributes from server first message");
         }
@@ -205,15 +205,15 @@ public abstract class ScramMechanism extends SaslMechanism {
             throw new AuthenticationException("Invalid salt in server first message");
         }
 
-        if (d != null && this.downgradeProtection != null) {
+        if (h != null && this.downgradeProtection != null) {
             final String asSeenInFeatures;
             try {
-                asSeenInFeatures = downgradeProtection.asDString();
+                asSeenInFeatures = downgradeProtection.asHString();
             } catch (final SecurityException e) {
                 throw new AuthenticationException(e);
             }
             final var hashed = BaseEncoding.base64().encode(digest(asSeenInFeatures.getBytes()));
-            if (!hashed.equals(d)) {
+            if (!hashed.equals(h)) {
                 throw new AuthenticationException("Mismatch in SSDP");
             }
         }

src/main/java/eu/siacs/conversations/entities/Account.java 🔗

@@ -306,7 +306,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
     }
 
     public String getUsername() {
-        return jid.getEscapedLocal();
+        return jid.getLocal();
     }
 
     public boolean setJid(final Jid next) {
@@ -330,7 +330,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
     }
 
     public String getServer() {
-        return jid.getDomain().toEscapedString();
+        return jid.getDomain().toString();
     }
 
     public String getPassword() {
@@ -546,7 +546,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) {
@@ -768,11 +768,11 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
 
     public String getShareableUri() {
         List<XmppUri.Fingerprint> fingerprints = this.getFingerprints();
-        String uri = "xmpp:" + Uri.encode(getJid().asBareJid().toEscapedString(), "@/+");
-        if (fingerprints.size() > 0) {
-            return XmppUri.getFingerprintUri(uri, fingerprints, ';');
-        } else {
+        final String uri = "xmpp:" + Uri.encode(this.getJid().asBareJid().toString(), "@/+");
+        if (fingerprints.isEmpty()) {
             return uri;
+        } else {
+            return XmppUri.getFingerprintUri(uri, fingerprints, ';');
         }
     }
 
@@ -780,11 +780,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, '&');
         }
     }
 

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.ArrayList;
 import java.util.Collections;
@@ -16,81 +18,76 @@ 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;
-	}
+    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);
@@ -98,7 +95,7 @@ public class Bookmark extends Element implements ListItem {
 			return null;
 		}
 		final Bookmark bookmark = new Bookmark(account);
-		bookmark.jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("id"));
+		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;
@@ -119,9 +116,9 @@ public class Bookmark extends Element implements ListItem {
 		return bookmark;
 	}
 
-	public Element getExtensions() {
-		return extensions;
-	}
+    public Element getExtensions() {
+        return extensions;
+    }
 
 	public void addGroup(final String group) {
 		addChild("group", "jabber:iq:roster").setContent(group);
@@ -172,32 +169,32 @@ public class Bookmark extends Element implements ListItem {
 				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;
-	}
+    @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() {
 		return getFullJid(getNick(), true);
@@ -236,32 +233,32 @@ public class Bookmark extends Element implements ListItem {
 		return tags;
 	}
 
-	public String getNick() {
-		return Strings.emptyToNull(this.findChildContent("nick"));
-	}
+    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 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 boolean autojoin() {
+        return this.getAttributeAsBoolean("autojoin");
+    }
 
-	public String getPassword() {
-		return this.findChildContent("password");
-	}
+    public String getPassword() {
+        return this.findChildContent("password");
+    }
 
-	public void setPassword(String password) {
-		Element element = this.findChild("password");
-		if (element != null) {
-			element.setContent(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) {
@@ -289,56 +286,57 @@ public class Bookmark extends Element implements ListItem {
 		}
 	}
 
-	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 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();
+    }
 }

src/main/java/eu/siacs/conversations/entities/Contact.java 🔗

@@ -16,7 +16,6 @@ import android.text.TextUtils;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
-
 import com.google.common.base.Strings;
 
 import org.json.JSONArray;
@@ -88,10 +87,21 @@ public class Contact implements ListItem, Blockable {
         setAccount(other.getAccount());
     }
 
-    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;
@@ -110,7 +120,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));
@@ -141,7 +151,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)),
@@ -154,7 +165,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() {
@@ -175,12 +187,14 @@ public class Contact implements ListItem, Blockable {
         ListItem bookmark = account.getBookmark(jid);
         if (bookmark != null) {
             return bookmark.getDisplayName();
+        } else if (!TextUtils.isEmpty(this.presenceName) && mutualPresenceSubscription()) {
+            return this.presenceName;
         } else if (!TextUtils.isEmpty(this.presenceName)) {
             return this.presenceName + (mutualPresenceSubscription() ? "" : " (" + jid + ")");
         } else if (jid.getLocal() != null) {
             return JidHelper.localPartOrFallback(jid);
         } else {
-            return jid.getDomain().toEscapedString();
+            return jid.getDomain().toString();
         }
     }
 
@@ -190,7 +204,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();
         }
     }
 
@@ -242,8 +256,9 @@ public class Contact implements ListItem, Blockable {
                     getDisplayName().toLowerCase(Locale.US).contains(parts[0]) ||
                     matchInTag(context, parts[0]);
         } else {
-            return jid.toString().contains(needle) ||
-                    getDisplayName().toLowerCase(Locale.US).contains(needle);
+            return jid.toString().contains(needle)
+                    || getDisplayName().toLowerCase(Locale.US).contains(needle)
+                    || matchInTag(context, needle);
         }
     }
 
@@ -437,8 +452,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));
     }
 
@@ -530,21 +545,25 @@ public class Contact implements ListItem, Blockable {
     }
 
     public String getServer() {
-        return getJid().getDomain().toEscapedString();
+        return getJid().getDomain().toString();
     }
 
-    public void setAvatar(Avatar avatar) {
-        setAvatar(avatar, false);
+    public boolean setAvatar(final Avatar avatar) {
+        return setAvatar(avatar, false);
     }
 
-    public void setAvatar(Avatar avatar, boolean previouslyOmittedPepFetch) {
+    public boolean setAvatar(final Avatar avatar, final boolean previouslyOmittedPepFetch) {
         if (this.avatar != null && this.avatar.equals(avatar)) {
-            return;
+            return false;
         }
-        if (!previouslyOmittedPepFetch && this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) {
-            return;
+        if (!previouslyOmittedPepFetch
+                && this.avatar != null
+                && this.avatar.origin == Avatar.Origin.PEP
+                && avatar.origin == Avatar.Origin.VCARD) {
+            return false;
         }
         this.avatar = avatar;
+        return true;
     }
 
     public String getAvatarFilename() {
@@ -720,7 +739,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

src/main/java/eu/siacs/conversations/entities/Conversation.java 🔗

@@ -287,8 +287,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)),
@@ -315,7 +314,7 @@ public class Conversation extends AbstractEntity
         if (conversation.getContact().isOwnServer()) {
             return false;
         }
-        final String contact = conversation.getJid().getDomain().toEscapedString();
+        final String contact = conversation.getJid().getDomain().toString();
         final String account = conversation.getAccount().getServer();
         if (Config.OMEMO_EXCEPTIONS.matchesContactDomain(contact)
                 || Config.OMEMO_EXCEPTIONS.ACCOUNT_DOMAINS.contains(account)) {
@@ -2206,7 +2205,7 @@ public class Conversation extends AbstractEntity
 
                     if (field.getType().equals(Optional.of("jid-single")) || field.getType().equals(Optional.of("jid-multi"))) {
                         binding.values.setOnItemClickListener((arg0, arg1, pos, id) -> {
-                            new FixedURLSpan("xmpp:" + Uri.encode(Jid.ofEscaped(values.getItem(pos).getValue()).toEscapedString(), "@/+"), account).onClick(binding.values);
+                            new FixedURLSpan("xmpp:" + Uri.encode(Jid.of(values.getItem(pos).getValue()).toString(), "@/+"), account).onClick(binding.values);
                         });
                     } else if ("xs:anyURI".equals(datatype)) {
                         binding.values.setOnItemClickListener((arg0, arg1, pos, id) -> {
@@ -2245,7 +2244,7 @@ public class Conversation extends AbstractEntity
                         String value = formatValue(datatype, cell.el.findChildContent("value", "jabber:x:data"), true);
                         SpannableStringBuilder text = new SpannableStringBuilder(value == null ? "" : value);
                         if (cell.reported.getType().equals(Optional.of("jid-single"))) {
-                            text.setSpan(new FixedURLSpan("xmpp:" + Jid.ofEscaped(text.toString()).toEscapedString(), account), 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                            text.setSpan(new FixedURLSpan("xmpp:" + Jid.of(text.toString()).toString(), account), 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                         } else if ("xs:anyURI".equals(datatype)) {
                             text.setSpan(new FixedURLSpan(text.toString(), account), 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                         } else if ("html:tel".equals(datatype)) {

src/main/java/eu/siacs/conversations/entities/Message.java 🔗

@@ -1813,8 +1813,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
         return encryption;
     }
 
-    public static boolean configurePrivateMessage(final Message message) {
-        return configurePrivateMessage(message, false);
+    public static void configurePrivateMessage(final Message message) {
+        configurePrivateMessage(message, false);
     }
 
     public static boolean configurePrivateFileMessage(final Message message) {
@@ -1822,27 +1822,19 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
     }
 
     private static boolean configurePrivateMessage(final Message message, final boolean isFile) {
-        final Conversation conversation;
-        if (message.conversation instanceof Conversation) {
-            conversation = (Conversation) message.conversation;
-        } else {
-            return false;
-        }
-        if (conversation.getMode() == Conversation.MODE_MULTI) {
-            final Jid nextCounterpart = conversation.getNextCounterpart();
-            return configurePrivateMessage(conversation, message, nextCounterpart, isFile);
+        if (message.conversation instanceof Conversation conversation) {
+            if (conversation.getMode() == Conversation.MODE_MULTI) {
+                final Jid nextCounterpart = conversation.getNextCounterpart();
+                return configurePrivateMessage(conversation, message, nextCounterpart, isFile);
+            }
         }
         return false;
     }
 
-    public static boolean configurePrivateMessage(final Message message, final Jid counterpart) {
-        final Conversation conversation;
-        if (message.conversation instanceof Conversation) {
-            conversation = (Conversation) message.conversation;
-        } else {
-            return false;
+    public static void configurePrivateMessage(final Message message, final Jid counterpart) {
+        if (message.conversation instanceof Conversation conversation) {
+            configurePrivateMessage(conversation, message, counterpart, false);
         }
-        return configurePrivateMessage(conversation, message, counterpart, false);
     }
 
     private static boolean configurePrivateMessage(
@@ -1854,7 +1846,16 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
             return false;
         }
         message.setCounterpart(counterpart);
-        message.setTrueCounterpart(conversation.getMucOptions().getTrueCounterpart(counterpart));
+        final var mucOptions = conversation.getMucOptions();
+        if (counterpart.equals(mucOptions.getSelf().getFullJid())) {
+            message.setTrueCounterpart(conversation.getAccount().getJid().asBareJid());
+        } else {
+            final var user = mucOptions.findUserByFullJid(counterpart);
+            if (user != null) {
+                message.setTrueCounterpart(user.getRealJid());
+                message.setOccupantId(user.getOccupantId());
+            }
+        }
         message.setType(isFile ? Message.TYPE_PRIVATE_FILE : Message.TYPE_PRIVATE);
         return true;
     }

src/main/java/eu/siacs/conversations/entities/MucOptions.java 🔗

@@ -12,7 +12,6 @@ import io.ipfs.cid.Cid;
 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;
@@ -71,16 +70,17 @@ public class MucOptions {
         return this.conversation.getAccount();
     }
 
-    public boolean setSelf(User user) {
+    public boolean setSelf(final 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());
         this.conversation.setAttribute("mucNick", user.getNick());
         return roleChanged || affiliationChanged;
     }
 
-    public void changeAffiliation(Jid jid, Affiliation affiliation) {
-        User user = findUserByRealJid(jid);
+    public void changeAffiliation(final Jid jid, final Affiliation affiliation) {
+        var user = findUserByRealJid(jid);
         synchronized (users) {
             if (user == null) {
                 user = new User(this, null, null, null, new HashSet<>());
@@ -130,21 +130,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);
     }
 
@@ -153,7 +162,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() {
@@ -161,7 +171,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();
     }
 
@@ -184,7 +195,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;
@@ -199,7 +210,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() {
@@ -211,7 +222,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() {
@@ -247,7 +260,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)
@@ -264,7 +278,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;
@@ -273,7 +287,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);
@@ -298,7 +312,10 @@ public class MucOptions {
                 if (old.hats != null && user.hats == null) user.hats = old.hats;
                 if (old.avatar != null && user.avatar == null) user.avatar = old.avatar;
             }
-            boolean fullJidIsSelf = isOnline && user.getFullJid() != null && user.getFullJid().equals(self.getFullJid());
+            boolean fullJidIsSelf =
+                    isOnline
+                            && user.getFullJid() != null
+                            && user.getFullJid().equals(self.getFullJid());
             if (!fullJidIsSelf) {
                 this.users.add(user);
                 return !realJidFound && user.realJid != null;
@@ -351,7 +368,9 @@ public class MucOptions {
 
     public User findUserByOccupantId(final String occupantId, final Jid counterpart) {
         synchronized (this.users) {
-            final var found = Strings.isNullOrEmpty(occupantId) ? null : Iterables.find(this.users, u -> occupantId.equals(u.occupantId),null);
+            final var found = Strings.isNullOrEmpty(occupantId)
+                    ? null
+                    : Iterables.find(this.users, u -> occupantId.equals(u.occupantId), null);
             if (Strings.isNullOrEmpty(occupantId) || found != null) return found;
             final var user = new User(this, counterpart, occupantId, null, new HashSet<>());
             user.setOnline(false);
@@ -372,7 +391,8 @@ public class MucOptions {
 
     public User findUser(ReadByMarker readByMarker) {
         if (readByMarker.getRealJid() != null) {
-            return findOrCreateUserByRealJid(readByMarker.getRealJid().asBareJid(), readByMarker.getFullJid(), null);
+            return findOrCreateUserByRealJid(
+                    readByMarker.getRealJid().asBareJid(), readByMarker.getFullJid(), null);
         } else if (readByMarker.getFullJid() != null) {
             return findUserByFullJid(readByMarker.getFullJid());
         } else {
@@ -396,7 +416,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);
@@ -470,13 +490,15 @@ public class MucOptions {
         }
     }
 
-    public List<User> getUsers(int max) {
-        ArrayList<User> subset = new ArrayList<>();
-        HashSet<Jid> jids = new HashSet<>();
-        jids.add(account.getJid().asBareJid());
+    public List<User> getUsers(final int max) {
+        final ArrayList<User> subset = new ArrayList<>();
+        final HashSet<Jid> addresses = new HashSet<>();
+        addresses.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
+                                && addresses.add(user.getRealJid()))) {
                     subset.add(user);
                 }
                 if (subset.size() >= max) {
@@ -492,7 +514,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) {
@@ -530,7 +553,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 {
@@ -718,7 +742,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 {
@@ -743,7 +768,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);
                 }
             }
@@ -859,9 +889,7 @@ public class MucOptions {
         void onFailure();
     }
 
-    public interface OnRenameListener extends OnEventListener {
-
-    }
+    public interface OnRenameListener extends OnEventListener {}
 
     public static class Hat implements Comparable<Hat> {
         private final Uri uri;
@@ -1014,7 +1042,7 @@ public class MucOptions {
             }
         }
 
-        public boolean setAvatar(Avatar avatar) {
+        public boolean setAvatar(final Avatar avatar) {
             if (occupantId != null) {
                 options.getConversation().setAttribute("occupantAvatar/" + occupantId, getContact() == null && avatar != null ? avatar.sha1sum : null);
             }
@@ -1030,7 +1058,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();
         }
 
@@ -1066,7 +1097,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() {
@@ -1084,7 +1114,13 @@ public class MucOptions {
 
         @Override
         public String toString() {
-            return "[fulljid:" + fullJid + ",realjid:" + realJid + ",nick:" + nick + ",affiliation" + affiliation.toString() + "]";
+            return "[fulljid:"
+                    + fullJid
+                    + ",realjid:"
+                    + realJid
+                    + ",affiliation"
+                    + affiliation.toString()
+                    + "]";
         }
 
         public boolean realJidMatchesAccount() {

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());
     }
-}
+}

src/main/java/eu/siacs/conversations/entities/Reaction.java 🔗

@@ -1,7 +1,6 @@
 package eu.siacs.conversations.entities;
 
 import android.util.Log;
-
 import androidx.annotation.NonNull;
 
 import com.cheogram.android.EmojiSearch;
@@ -29,7 +28,6 @@ import io.ipfs.cid.Cid;
 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;
@@ -178,7 +176,7 @@ public class Reaction {
             if (value == null) {
                 out.nullValue();
             } else {
-                out.value(value.toEscapedString());
+                out.value(value.toString());
             }
         }
 
@@ -189,7 +187,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");
         }

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();
     }
-}
+}

src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java 🔗

@@ -1,7 +1,6 @@
 package eu.siacs.conversations.generator;
 
 import android.util.Base64;
-
 import eu.siacs.conversations.BuildConfig;
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
@@ -10,7 +9,6 @@ import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.xml.Namespace;
 import eu.siacs.conversations.xmpp.XmppConnection;
-
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.text.SimpleDateFormat;
@@ -40,7 +38,8 @@ public abstract class AbstractGenerator {
         Namespace.NICK + "+notify",
         "urn:xmpp:ping",
         "jabber:iq:version",
-        "http://jabber.org/protocol/chatstates"
+        "http://jabber.org/protocol/chatstates",
+        Namespace.REACTIONS
     };
     private final String[] MESSAGE_CONFIRMATION_FEATURES = {
         "urn:xmpp:chat-markers:0", "urn:xmpp:receipts"

src/main/java/eu/siacs/conversations/generator/IqGenerator.java 🔗

@@ -181,7 +181,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;
     }
@@ -194,7 +194,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);
@@ -204,8 +205,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);
@@ -292,7 +292,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;
     }
@@ -315,8 +315,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);
@@ -325,21 +329,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);
@@ -347,13 +356,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);
     }
 
@@ -366,7 +378,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();
@@ -395,7 +407,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);
@@ -499,7 +512,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;
             }
@@ -508,7 +523,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());
@@ -534,7 +550,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);
@@ -581,7 +597,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;
     }
 
@@ -593,9 +611,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;
     }
 
@@ -606,9 +624,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;
     }
 
@@ -648,7 +666,7 @@ public class IqGenerator extends AbstractGenerator {
     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;
     }
 

src/main/java/eu/siacs/conversations/parser/AbstractParser.java 🔗

@@ -15,80 +15,82 @@ 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;
 
 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) {
@@ -121,107 +123,107 @@ 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,null,null,new Element("hats", "urn:xmpp:hats:0"));
-	}
-
-	public static MucOptions.User parseItem(final Conversation conference, Element item, Jid fullJid, final Element occupantId, final String nicknameIn, final Element hatsEl) {
-		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;
-			}
-		}
-		Jid realJid = item.getAttributeAsJid("jid");
-		if (fullJid != null) nick = fullJid.getResource();
-		String nickname = null;
-		if (nick != null && nicknameIn != null) nickname = nick.equals(nicknameIn) ? nick : null;
-		try {
-			if (nickname == null && nicknameIn != null && nick != null && gnu.inet.encoding.Punycode.decode(nick).equals(nicknameIn)) {
-				nickname = nicknameIn;
-			}
-		} catch (final Exception e) { }
-		Set<MucOptions.Hat> hats = new TreeSet<>();
-		for (Element hat : hatsEl.getChildren()) {
-			if ("hat".equals(hat.getName()) && ("urn:xmpp:hats:0".equals(hat.getNamespace()) || "xmpp:prosody.im/protocol/hats:1".equals(hat.getNamespace()))) {
-				hats.add(new MucOptions.Hat(hat));
-			}
-		}
-		MucOptions.User user = new MucOptions.User(conference.getMucOptions(), fullJid, occupantId == null ? null : occupantId.getAttribute("id"), nickname, hatsEl == null ? null : hats);
-		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,null,null,new Element("hats", "urn:xmpp:hats:0"));
+    }
+
+    public static MucOptions.User parseItem(final Conversation conference, Element item, Jid fullJid, final Element occupantId, final String nicknameIn, final Element hatsEl) {
+        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;
+            }
+        }
+        Jid realJid = item.getAttributeAsJid("jid");
+        if (fullJid != null) nick = fullJid.getResource();
+        String nickname = null;
+        if (nick != null && nicknameIn != null) nickname = nick.equals(nicknameIn) ? nick : null;
+        try {
+            if (nickname == null && nicknameIn != null && nick != null && gnu.inet.encoding.Punycode.decode(nick).equals(nicknameIn)) {
+                nickname = nicknameIn;
+            }
+        } catch (final Exception e) { }
+        Set<MucOptions.Hat> hats = new TreeSet<>();
+        for (Element hat : hatsEl.getChildren()) {
+            if ("hat".equals(hat.getName()) && ("urn:xmpp:hats:0".equals(hat.getNamespace()) || "xmpp:prosody.im/protocol/hats:1".equals(hat.getNamespace()))) {
+                hats.add(new MucOptions.Hat(hat));
+            }
+        }
+        MucOptions.User user = new MucOptions.User(conference.getMucOptions(), fullJid, occupantId == null ? null : occupantId.getAttribute("id"), nickname, hatsEl == null ? null : hats);
+        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;
+    }
 }

src/main/java/eu/siacs/conversations/parser/IqParser.java 🔗

@@ -3,18 +3,21 @@ 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 eu.siacs.conversations.Config;
+import eu.siacs.conversations.crypto.axolotl.AxolotlService;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Contact;
+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.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;
@@ -37,12 +40,17 @@ 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 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> {
 
     public IqParser(final XmppConnectionService service, final Account account) {
@@ -95,8 +103,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) {
@@ -106,14 +113,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);
@@ -127,9 +136,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());
@@ -182,7 +197,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...");
                     }
                 }
             }
@@ -211,7 +234,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;
     }
@@ -224,7 +252,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;
         }
     }
@@ -237,7 +267,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;
         }
     }
@@ -246,7 +281,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");
@@ -255,12 +295,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();
@@ -273,9 +323,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;
@@ -287,7 +350,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) {
@@ -301,7 +365,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));
@@ -333,8 +401,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) {
@@ -343,8 +418,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));
             }
         }
 
@@ -364,15 +438,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();
@@ -383,7 +461,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);
                         }
@@ -406,10 +485,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();
@@ -417,7 +498,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);
                         }
@@ -431,10 +513,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);
@@ -453,7 +535,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 =
@@ -485,5 +568,4 @@ public class IqParser extends AbstractParser implements Consumer<Iq> {
             }
         }
     }
-
 }

src/main/java/eu/siacs/conversations/parser/MessageParser.java 🔗

@@ -59,25 +59,27 @@ import eu.siacs.conversations.utils.Emoticons;
 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.OnMessagePacketReceived;
 import eu.siacs.conversations.xmpp.chatstate.ChatState;
 import eu.siacs.conversations.xmpp.forms.Data;
 import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
 import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
 import eu.siacs.conversations.xmpp.pep.Avatar;
 import im.conversations.android.xmpp.model.Extension;
+import im.conversations.android.xmpp.model.axolotl.Encrypted;
 import im.conversations.android.xmpp.model.carbons.Received;
 import im.conversations.android.xmpp.model.carbons.Sent;
 import im.conversations.android.xmpp.model.correction.Replace;
 import im.conversations.android.xmpp.model.forward.Forwarded;
+import im.conversations.android.xmpp.model.markers.Displayed;
 import im.conversations.android.xmpp.model.occupant.OccupantId;
 import im.conversations.android.xmpp.model.reactions.Reactions;
 
-public class MessageParser extends AbstractParser implements Consumer<im.conversations.android.xmpp.model.stanza.Message> {
+public class MessageParser extends AbstractParser
+        implements Consumer<im.conversations.android.xmpp.model.stanza.Message> {
 
-    private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH);
+    private static final SimpleDateFormat TIME_FORMAT =
+            new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH);
 
     private static final List<String> JINGLE_MESSAGE_ELEMENT_NAMES =
             Arrays.asList("accept", "propose", "proceed", "reject", "retract", "ringing", "finish");
@@ -86,7 +88,8 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
         super(service, account);
     }
 
-    private static String extractStanzaId(Element packet, boolean isTypeGroupChat, Conversation conversation) {
+    private static String extractStanzaId(
+            Element packet, boolean isTypeGroupChat, Conversation conversation) {
         final Jid by;
         final boolean safeToExtract;
         if (isTypeGroupChat) {
@@ -109,7 +112,7 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
         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");
             }
         }
@@ -118,11 +121,15 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
 
     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"));
+        Jid result =
+                item == null ? null : Jid.Invalid.getNullForInvalid(item.getAttributeAsJid("jid"));
         return result != null ? result : fallback;
     }
 
-    private boolean extractChatState(Conversation c, final boolean isTypeGroupChat, final im.conversations.android.xmpp.model.stanza.Message packet) {
+    private boolean extractChatState(
+            Conversation c,
+            final boolean isTypeGroupChat,
+            final im.conversations.android.xmpp.model.stanza.Message packet) {
         ChatState state = ChatState.parse(packet);
         if (state != null && c != null) {
             final Account account = c.getAccount();
@@ -153,45 +160,76 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
         return false;
     }
 
-    private Message parseAxolotlChat(Element axolotlMessage, Jid from, Conversation conversation, int status, final boolean checkedForDuplicates, boolean postpone) {
+    private Message parseAxolotlChat(
+            final Encrypted axolotlMessage,
+            final Jid from,
+            final Conversation conversation,
+            final int status,
+            final boolean checkedForDuplicates,
+            final boolean postpone) {
         final AxolotlService service = conversation.getAccount().getAxolotlService();
         final XmppAxolotlMessage xmppAxolotlMessage;
         try {
             xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlMessage, from.asBareJid());
-        } catch (Exception e) {
-            Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": invalid omemo message received " + e.getMessage());
+        } catch (final Exception e) {
+            Log.d(
+                    Config.LOGTAG,
+                    conversation.getAccount().getJid().asBareJid()
+                            + ": invalid omemo message received "
+                            + e.getMessage());
             return null;
         }
         if (xmppAxolotlMessage.hasPayload()) {
             final XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage;
             try {
-                plaintextMessage = service.processReceivingPayloadMessage(xmppAxolotlMessage, postpone);
+                plaintextMessage =
+                        service.processReceivingPayloadMessage(xmppAxolotlMessage, postpone);
             } catch (BrokenSessionException e) {
                 if (checkedForDuplicates) {
                     if (service.trustedOrPreviouslyResponded(from.asBareJid())) {
                         service.reportBrokenSessionException(e, postpone);
-                        return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
+                        return new Message(
+                                conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
                     } else {
-                        Log.d(Config.LOGTAG, "ignoring broken session exception because contact was not trusted");
-                        return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
+                        Log.d(
+                                Config.LOGTAG,
+                                "ignoring broken session exception because contact was not"
+                                        + " trusted");
+                        return new Message(
+                                conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
                     }
                 } else {
-                    Log.d(Config.LOGTAG, "ignoring broken session exception because checkForDuplicates failed");
+                    Log.d(
+                            Config.LOGTAG,
+                            "ignoring broken session exception because checkForDuplicates failed");
                     return null;
                 }
             } catch (NotEncryptedForThisDeviceException e) {
-                return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE, status);
+                return new Message(
+                        conversation, "", Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE, status);
             } catch (OutdatedSenderException e) {
                 return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
             }
             if (plaintextMessage != null) {
-                Message finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status);
+                Message finishedMessage =
+                        new Message(
+                                conversation,
+                                plaintextMessage.getPlaintext(),
+                                Message.ENCRYPTION_AXOLOTL,
+                                status);
                 finishedMessage.setFingerprint(plaintextMessage.getFingerprint());
-                Log.d(Config.LOGTAG, AxolotlService.getLogprefix(finishedMessage.getConversation().getAccount()) + " Received Message with session fingerprint: " + plaintextMessage.getFingerprint());
+                Log.d(
+                        Config.LOGTAG,
+                        AxolotlService.getLogprefix(finishedMessage.getConversation().getAccount())
+                                + " Received Message with session fingerprint: "
+                                + plaintextMessage.getFingerprint());
                 return finishedMessage;
             }
         } else {
-            Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": received OMEMO key transport message");
+            Log.d(
+                    Config.LOGTAG,
+                    conversation.getAccount().getJid().asBareJid()
+                            + ": received OMEMO key transport message");
             service.processReceivingKeyTransportMessage(xmppAxolotlMessage, postpone);
         }
         return null;
@@ -203,13 +241,13 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
             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);
+                    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;
                 }
@@ -218,8 +256,8 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
         }
         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;
             }
@@ -246,11 +284,12 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                         mXmppConnectionService.updateAccountUi();
                     } else {
                         final Contact contact = account.getRoster().getContact(from);
-                        contact.setAvatar(avatar);
-                        mXmppConnectionService.syncRoster(account);
-                        mXmppConnectionService.getAvatarService().clear(contact);
-                        mXmppConnectionService.updateConversationUi();
-                        mXmppConnectionService.updateRosterUi(XmppConnectionService.UpdateRosterReason.AVATAR);
+                        if (contact.setAvatar(avatar)) {
+                            mXmppConnectionService.syncRoster(account);
+                            mXmppConnectionService.getAvatarService().clear(contact);
+                            mXmppConnectionService.updateConversationUi();
+                            mXmppConnectionService.updateRosterUi(XmppConnectionService.UpdateRosterReason.AVATAR);
+                        }
                     }
                 } else if (mXmppConnectionService.isDataSaverDisabled()) {
                     mXmppConnectionService.fetchAvatar(account, avatar);
@@ -265,7 +304,14 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
         } else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) {
             Element item = items.findChild("item");
             final Set<Integer> deviceIds = IqParser.deviceIds(item);
-            Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received PEP device list " + deviceIds + " update from " + from + ", processing... ");
+            Log.d(
+                    Config.LOGTAG,
+                    AxolotlService.getLogprefix(account)
+                            + "Received PEP device list "
+                            + deviceIds
+                            + " update from "
+                            + from
+                            + ", processing... ");
             final AxolotlService axolotlService = account.getAxolotlService();
             axolotlService.registerDevices(from, deviceIds);
         } else if (Namespace.BOOKMARKS.equals(node) && account.getJid().asBareJid().equals(from)) {
@@ -275,7 +321,8 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                     Log.w(
                             Config.LOGTAG,
                             account.getJid().asBareJid()
-                                    + ": received storage:bookmark notification even though we opted into bookmarks:1");
+                                    + ": received storage:bookmark notification even though we"
+                                    + " opted into bookmarks:1");
                 }
                 final Element i = items.findChild("item");
                 final Element storage =
@@ -289,7 +336,8 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                 Log.d(
                         Config.LOGTAG,
                         account.getJid().asBareJid()
-                                + ": ignoring bookmark PEP event because bookmark conversion was not detected");
+                                + ": ignoring bookmark PEP event because bookmark conversion was"
+                                + " not detected");
             }
         } else if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
             final Element item = items.findChild("item");
@@ -303,10 +351,12 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                 }
             }
             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(Config.LOGTAG, account.getJid().asBareJid() + ": deleted bookmark for " + id);
+                    Log.d(
+                            Config.LOGTAG,
+                            account.getJid().asBareJid() + ": deleted bookmark for " + id);
                     mXmppConnectionService.processDeletedBookmark(account, id);
                     mXmppConnectionService.updateConversationUi();
                 }
@@ -334,8 +384,9 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
         } else if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
             Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": deleted bookmarks node");
             deleteAllBookmarks(account);
-        } else if (Namespace.AVATAR_METADATA.equals(node) && account.getJid().asBareJid().equals(from)) {
-            Log.d(Config.LOGTAG,account.getJid().asBareJid()+": deleted avatar metadata node");
+        } else if (Namespace.AVATAR_METADATA.equals(node)
+                && account.getJid().asBareJid().equals(from)) {
+            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": deleted avatar metadata node");
         }
     }
 
@@ -372,10 +423,13 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
         mXmppConnectionService.updateAccountUi();
     }
 
-    private boolean handleErrorMessage(final Account account, final im.conversations.android.xmpp.model.stanza.Message packet) {
+    private boolean handleErrorMessage(
+            final Account account,
+            final im.conversations.android.xmpp.model.stanza.Message packet) {
         if (packet.getType() == im.conversations.android.xmpp.model.stanza.Message.Type.ERROR) {
             if (packet.fromServer(account)) {
-                final var forwarded = getForwardedMessagePacket(packet,"received", Namespace.CARBONS);
+                final var forwarded =
+                        getForwardedMessagePacket(packet, "received", Namespace.CARBONS);
                 if (forwarded != null) {
                     return handleErrorMessage(account, forwarded.first);
                 }
@@ -384,29 +438,51 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
             final String id = packet.getId();
             if (from != null && id != null) {
                 if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX)) {
-                    final String sessionId = id.substring(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX.length());
-                    mXmppConnectionService.getJingleConnectionManager()
-                            .updateProposedSessionDiscovered(account, from, sessionId, JingleConnectionManager.DeviceDiscoveryState.FAILED);
+                    final String sessionId =
+                            id.substring(
+                                    JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX.length());
+                    mXmppConnectionService
+                            .getJingleConnectionManager()
+                            .updateProposedSessionDiscovered(
+                                    account,
+                                    from,
+                                    sessionId,
+                                    JingleConnectionManager.DeviceDiscoveryState.FAILED);
                     return true;
                 }
                 if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_PROCEED_ID_PREFIX)) {
-                    final String sessionId = id.substring(JingleRtpConnection.JINGLE_MESSAGE_PROCEED_ID_PREFIX.length());
+                    final String sessionId =
+                            id.substring(
+                                    JingleRtpConnection.JINGLE_MESSAGE_PROCEED_ID_PREFIX.length());
                     final String message = extractErrorMessage(packet);
-                    mXmppConnectionService.getJingleConnectionManager().failProceed(account, from, sessionId, message);
+                    mXmppConnectionService
+                            .getJingleConnectionManager()
+                            .failProceed(account, from, sessionId, message);
                     return true;
                 }
-                mXmppConnectionService.markMessage(account,
+                mXmppConnectionService.markMessage(
+                        account,
                         from.asBareJid(),
                         id,
                         Message.STATUS_SEND_FAILED,
                         extractErrorMessage(packet));
                 final Element error = packet.findChild("error");
-                final boolean pingWorthyError = error != null && (error.hasChild("not-acceptable") || error.hasChild("remote-server-timeout") || error.hasChild("remote-server-not-found"));
+                final boolean pingWorthyError =
+                        error != null
+                                && (error.hasChild("not-acceptable")
+                                        || error.hasChild("remote-server-timeout")
+                                        || error.hasChild("remote-server-not-found"));
                 if (pingWorthyError) {
                     Conversation conversation = mXmppConnectionService.find(account, from);
-                    if (conversation != null && conversation.getMode() == Conversational.MODE_MULTI) {
+                    if (conversation != null
+                            && conversation.getMode() == Conversational.MODE_MULTI) {
                         if (conversation.getMucOptions().online()) {
-                            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received ping worthy error for seemingly online muc at " + from);
+                            Log.d(
+                                    Config.LOGTAG,
+                                    account.getJid().asBareJid()
+                                            + ": received ping worthy error for seemingly online"
+                                            + " muc at "
+                                            + from);
                             mXmppConnectionService.mucSelfPingAndRejoin(conversation);
                         }
                     }
@@ -426,17 +502,24 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
         Long timestamp = null;
         boolean isCarbon = false;
         String serverMsgId = null;
-        final Element fin = original.findChild("fin", MessageArchiveService.Version.MAM_0.namespace);
+        final Element fin =
+                original.findChild("fin", MessageArchiveService.Version.MAM_0.namespace);
         if (fin != null) {
-            mXmppConnectionService.getMessageArchiveService().processFinLegacy(fin, original.getFrom());
+            mXmppConnectionService
+                    .getMessageArchiveService()
+                    .processFinLegacy(fin, original.getFrom());
             return;
         }
         final Element result = MessageArchiveService.Version.findResult(original);
         final String queryId = result == null ? null : result.getAttribute("queryid");
-        final MessageArchiveService.Query query = queryId == null ? null : mXmppConnectionService.getMessageArchiveService().findQuery(queryId);
-        final boolean offlineMessagesRetrieved = account.getXmppConnection().isOfflineMessagesRetrieved();
+        final MessageArchiveService.Query query =
+                queryId == null
+                        ? null
+                        : mXmppConnectionService.getMessageArchiveService().findQuery(queryId);
+        final boolean offlineMessagesRetrieved =
+                account.getXmppConnection().isOfflineMessagesRetrieved();
         if (query != null && query.validFrom(original.getFrom())) {
-            final var f = getForwardedMessagePacket(original,"result", query.version.namespace);
+            final var f = getForwardedMessagePacket(original, "result", query.version.namespace);
             if (f == null) {
                 return;
             }
@@ -447,16 +530,24 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
             if (handleErrorMessage(account, packet)) {
                 return;
             }
-            final var contact = packet.getFrom() == null || packet.getFrom() instanceof InvalidJid ? null : account.getRoster().getContact(packet.getFrom());
+            final var contact = packet.getFrom() == null || packet.getFrom() instanceof Jid.Invalid ? null : account.getRoster().getContact(packet.getFrom());
             if (contact != null && contact.isBlocked()) {
                 Log.d(Config.LOGTAG, "Got MAM result from blocked contact, ignoring...");
                 return;
             }
         } else if (query != null) {
-            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received mam result with invalid from (" + original.getFrom() + ") or queryId (" + queryId + ")");
+            Log.d(
+                    Config.LOGTAG,
+                    account.getJid().asBareJid()
+                            + ": received mam result with invalid from ("
+                            + original.getFrom()
+                            + ") or queryId ("
+                            + queryId
+                            + ")");
             return;
         } else if (original.fromServer(account)
-                && original.getType() != im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT) {
+                && original.getType()
+                        != im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT) {
             Pair<im.conversations.android.xmpp.model.stanza.Message, Long> f;
             f = getForwardedMessagePacket(original, Received.class);
             f = f == null ? getForwardedMessagePacket(original, Sent.class) : f;
@@ -471,10 +562,14 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
         }
 
         if (timestamp == null) {
-            timestamp = AbstractParser.parseTimestamp(original, AbstractParser.parseTimestamp(packet));
+            timestamp =
+                    AbstractParser.parseTimestamp(original, AbstractParser.parseTimestamp(packet));
         }
+
         final Element mucUserElement = packet.findChild("x", Namespace.MUC_USER);
-        final boolean isTypeGroupChat = packet.getType() == im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT;
+        final boolean isTypeGroupChat =
+                packet.getType()
+                        == im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT;
         final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted");
 
         Element replaceElement = packet.findChild("replace", "urn:xmpp:message-correct:0");
@@ -512,7 +607,7 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
 
         final var reactions = packet.getExtension(Reactions.class);
 
-        final Element axolotlEncrypted = packet.findChildEnsureSingle(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
+        final var axolotlEncrypted = packet.getOnlyExtension(Encrypted.class);
         int status;
         final Jid counterpart;
         final Jid to = packet.getTo();
@@ -531,12 +626,17 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
             html = null;
         }
 
-        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;
         }
         if (query != null && !query.muc() && isTypeGroupChat) {
-            Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": received groupchat (" + from + ") message on regular MAM request. skipping");
+            Log.e(
+                    Config.LOGTAG,
+                    account.getJid().asBareJid()
+                            + ": received groupchat ("
+                            + from
+                            + ") message on regular MAM request. skipping");
             return;
         }
         final Jid mucTrueCounterPart;
@@ -561,11 +661,25 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                                     ? mucUserElement
                                     : null,
                             mucTrueCounterPartByPresence);
+        } else if (mucUserElement != null) {
+            final Conversation conversation =
+                    mXmppConnectionService.find(account, from.asBareJid());
+            if (conversation != null) {
+                final var mucOptions = conversation.getMucOptions();
+                occupant = mucOptions.occupantId() ? packet.getExtension(OccupantId.class) : null;
+            } else {
+                occupant = null;
+            }
+            mucTrueCounterPart = null;
         } else {
             mucTrueCounterPart = null;
             occupant = null;
         }
-        boolean isMucStatusMessage = InvalidJid.hasValidFrom(packet) && from.isBareJid() && mucUserElement != null && mucUserElement.hasChild("status");
+        boolean isMucStatusMessage =
+                Jid.Invalid.hasValidFrom(packet)
+                        && from.isBareJid()
+                        && mucUserElement != null
+                        && mucUserElement.hasChild("status");
         boolean selfAddressed;
         if (packet.fromAccount(account)) {
             status = Message.STATUS_SEND;
@@ -584,18 +698,36 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
         final Invite invite = extractInvite(packet);
         if (invite != null) {
             if (invite.jid.asBareJid().equals(account.getJid().asBareJid())) {
-                Log.d(Config.LOGTAG,account.getJid().asBareJid()+": ignore invite to "+invite.jid+" because it matches account");
+                Log.d(
+                        Config.LOGTAG,
+                        account.getJid().asBareJid()
+                                + ": ignore invite to "
+                                + invite.jid
+                                + " because it matches account");
             } else if (isTypeGroupChat) {
-                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring invite to " + invite.jid + " because it was received as group chat");
-            } else if (invite.direct && (mucUserElement != null || invite.inviter == null || mXmppConnectionService.isMuc(account, invite.inviter))) {
-                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring direct invite to " + invite.jid + " because it was received in MUC");
+                Log.d(
+                        Config.LOGTAG,
+                        account.getJid().asBareJid()
+                                + ": ignoring invite to "
+                                + invite.jid
+                                + " because it was received as group chat");
+            } else if (invite.direct
+                    && (mucUserElement != null
+                            || invite.inviter == null
+                            || mXmppConnectionService.isMuc(account, invite.inviter))) {
+                Log.d(
+                        Config.LOGTAG,
+                        account.getJid().asBareJid()
+                                + ": ignoring direct invite to "
+                                + invite.jid
+                                + " because it was received in MUC");
             } else {
                 invite.execute(account);
                 return;
             }
         }
 
-        final boolean conversationIsProbablyMuc = isTypeGroupChat || mucUserElement != null || account.getXmppConnection().getMucServersWithholdAccount().contains(counterpart.getDomain().toEscapedString());
+        final boolean conversationIsProbablyMuc = isTypeGroupChat || mucUserElement != null || account.getXmppConnection().getMucServersWithholdAccount().contains(counterpart.getDomain().toString());
         final Element webxdc = packet.findChild("x", "urn:xmpp:webxdc:0");
         final Element thread = packet.findChild("thread");
         if (webxdc != null && thread != null) {
@@ -643,15 +775,25 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
             }
         }
 
-        if (reactions == null && (body != null || pgpEncrypted != null || (axolotlEncrypted != null && axolotlEncrypted.hasChild("payload")) || !attachments.isEmpty() || html != null || (packet.hasChild("subject") && packet.hasChild("thread"))) && !isMucStatusMessage) {
-            final Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.asBareJid(), conversationIsProbablyMuc, false, query, false);
+        if (reactions == null && (body != null
+                        || pgpEncrypted != null
+                        || (axolotlEncrypted != null && axolotlEncrypted.hasChild("payload"))
+                        || !attachments.isEmpty() || html != null || (packet.hasChild("subject") && packet.hasChild("thread")))
+                && !isMucStatusMessage) {
+            final Conversation conversation =
+                    mXmppConnectionService.findOrCreateConversation(
+                            account,
+                            counterpart.asBareJid(),
+                            conversationIsProbablyMuc,
+                            false,
+                            query,
+                            false);
             final boolean conversationMultiMode = conversation.getMode() == Conversation.MODE_MULTI;
 
             if (serverMsgId == null) {
                 serverMsgId = extractStanzaId(packet, isTypeGroupChat, conversation);
             }
 
-
             if (selfAddressed) {
                 // don’t store serverMsgId on reflections for edits
                 final var reflectedServerMsgId =
@@ -664,7 +806,8 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                     return;
                 }
                 status = Message.STATUS_RECEIVED;
-                if (remoteMsgId != null && conversation.findMessageWithRemoteId(remoteMsgId, counterpart) != null) {
+                if (remoteMsgId != null
+                        && conversation.findMessageWithRemoteId(remoteMsgId, counterpart) != null) {
                     return;
                 }
             }
@@ -672,7 +815,7 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
             if (isTypeGroupChat) {
                 if (conversation.getMucOptions().isSelf(counterpart)) {
                     status = Message.STATUS_SEND_RECEIVED;
-                    isCarbon = true; //not really carbon but received from another resource
+                    isCarbon = true; // not really carbon but received from another resource
                     // don’t store serverMsgId on reflections for edits
                     final var reflectedServerMsgId =
                             Strings.isNullOrEmpty(replacementId) ? serverMsgId : null;
@@ -698,17 +841,25 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                 Jid origin;
                 Set<Jid> fallbacksBySourceId = Collections.emptySet();
                 if (conversationMultiMode) {
-                    final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
+                    final Jid fallback =
+                            conversation.getMucOptions().getTrueCounterpart(counterpart);
                     origin = getTrueCounterpart(query != null ? mucUserElement : null, fallback);
                     if (origin == null) {
                         try {
-                            fallbacksBySourceId = account.getAxolotlService().findCounterpartsBySourceId(XmppAxolotlMessage.parseSourceId(axolotlEncrypted));
+                            fallbacksBySourceId =
+                                    account.getAxolotlService()
+                                            .findCounterpartsBySourceId(
+                                                    XmppAxolotlMessage.parseSourceId(
+                                                            axolotlEncrypted));
                         } catch (IllegalArgumentException e) {
-                            //ignoring
+                            // ignoring
                         }
                     }
-                    if (origin == null && fallbacksBySourceId.size() == 0) {
-                        Log.d(Config.LOGTAG, "axolotl message in anonymous conference received and no possible fallbacks");
+                    if (origin == null && fallbacksBySourceId.isEmpty()) {
+                        Log.d(
+                                Config.LOGTAG,
+                                "axolotl message in anonymous conference received and no possible"
+                                        + " fallbacks");
                         return;
                     }
                 } else {
@@ -716,17 +867,40 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                     origin = from;
                 }
 
-                final boolean liveMessage = query == null && !isTypeGroupChat && mucUserElement == null;
-                final boolean checkedForDuplicates = liveMessage || (serverMsgId != null && remoteMsgId != null && !conversation.possibleDuplicate(serverMsgId, remoteMsgId));
+                final boolean liveMessage =
+                        query == null && !isTypeGroupChat && mucUserElement == null;
+                final boolean checkedForDuplicates =
+                        liveMessage
+                                || (serverMsgId != null
+                                        && remoteMsgId != null
+                                        && !conversation.possibleDuplicate(
+                                                serverMsgId, remoteMsgId));
 
                 if (origin != null) {
-                    message = parseAxolotlChat(axolotlEncrypted, origin, conversation, status, checkedForDuplicates, query != null);
+                    message =
+                            parseAxolotlChat(
+                                    axolotlEncrypted,
+                                    origin,
+                                    conversation,
+                                    status,
+                                    checkedForDuplicates,
+                                    query != null);
                 } else {
                     Message trial = null;
                     for (Jid fallback : fallbacksBySourceId) {
-                        trial = parseAxolotlChat(axolotlEncrypted, fallback, conversation, status, checkedForDuplicates && fallbacksBySourceId.size() == 1, query != null);
+                        trial =
+                                parseAxolotlChat(
+                                        axolotlEncrypted,
+                                        fallback,
+                                        conversation,
+                                        status,
+                                        checkedForDuplicates && fallbacksBySourceId.size() == 1,
+                                        query != null);
                         if (trial != null) {
-                            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": decoded muc message using fallback");
+                            Log.d(
+                                    Config.LOGTAG,
+                                    account.getJid().asBareJid()
+                                            + ": decoded muc message using fallback");
                             origin = fallback;
                             break;
                         }
@@ -734,15 +908,26 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                     message = trial;
                 }
                 if (message == null) {
-                    if (query == null && extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet)) {
+                    if (query == null
+                            && extractChatState(
+                                    mXmppConnectionService.find(account, counterpart.asBareJid()),
+                                    isTypeGroupChat,
+                                    packet)) {
                         mXmppConnectionService.updateConversationUi();
                     }
                     if (query != null && status == Message.STATUS_SEND && remoteMsgId != null) {
                         Message previouslySent = conversation.findSentMessageWithUuid(remoteMsgId);
-                        if (previouslySent != null && previouslySent.getServerMsgId() == null && serverMsgId != null) {
+                        if (previouslySent != null
+                                && previouslySent.getServerMsgId() == null
+                                && serverMsgId != null) {
                             previouslySent.setServerMsgId(serverMsgId);
-                            mXmppConnectionService.databaseBackend.updateMessage(previouslySent, false);
-                            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": encountered previously sent OMEMO message without serverId. updating...");
+                            mXmppConnectionService.databaseBackend.updateMessage(
+                                    previouslySent, false);
+                            Log.d(
+                                    Config.LOGTAG,
+                                    account.getJid().asBareJid()
+                                            + ": encountered previously sent OMEMO message without"
+                                            + " serverId. updating...");
                         }
                     }
                     return;
@@ -766,7 +951,7 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
 
                     if (address.getAttribute("type").equals("ofrom") && address.getAttribute("jid") != null) {
                         Jid ofrom = address.getAttributeAsJid("jid");
-                        if (InvalidJid.isValid(ofrom) && ofrom.getDomain().equals(counterpart.getDomain()) &&
+                        if (Jid.Invalid.isValid(ofrom) && ofrom.getDomain().equals(counterpart.getDomain()) &&
                             conversation.getAccount().getRoster().getContact(counterpart.getDomain()).getPresences().anySupport("http://jabber.org/protocol/address")) {
 
                             message.setTrueCounterpart(ofrom);
@@ -830,7 +1015,10 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                 }
                 if (trueCounterpart != null && isTypeGroupChat) {
                     if (trueCounterpart.asBareJid().equals(account.getJid().asBareJid())) {
-                        status = isTypeGroupChat ? Message.STATUS_SEND_RECEIVED : Message.STATUS_SEND;
+                        status =
+                                isTypeGroupChat
+                                        ? Message.STATUS_SEND_RECEIVED
+                                        : Message.STATUS_SEND;
                     } else {
                         status = Message.STATUS_RECEIVED;
                         message.setCarbon(false);
@@ -846,19 +1034,32 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
             }
 
             if (replacementId != null && mXmppConnectionService.allowMessageCorrection()) {
-                final Message replacedMessage = conversation.findMessageWithRemoteIdAndCounterpart(replacementId, counterpart);
+                final Message replacedMessage =
+                        conversation.findMessageWithRemoteIdAndCounterpart(
+                                replacementId,
+                                counterpart);
                 if (replacedMessage != null) {
-                    final boolean fingerprintsMatch = replacedMessage.getFingerprint() == null
-                            || replacedMessage.getFingerprint().equals(message.getFingerprint());
-                    final boolean trueCountersMatch = replacedMessage.getTrueCounterpart() != null
-                            && message.getTrueCounterpart() != null
-                            && replacedMessage.getTrueCounterpart().asBareJid().equals(message.getTrueCounterpart().asBareJid());
+                    final boolean fingerprintsMatch =
+                            replacedMessage.getFingerprint() == null
+                                    || replacedMessage
+                                            .getFingerprint()
+                                            .equals(message.getFingerprint());
+                    final boolean trueCountersMatch =
+                            replacedMessage.getTrueCounterpart() != null
+                                    && message.getTrueCounterpart() != null
+                                    && replacedMessage
+                                            .getTrueCounterpart()
+                                            .asBareJid()
+                                            .equals(message.getTrueCounterpart().asBareJid());
                     final boolean occupantIdMatch =
                             replacedMessage.getOccupantId() != null
                                     && replacedMessage
                                             .getOccupantId()
                                             .equals(message.getOccupantId());
-                    final boolean mucUserMatches = query == null && replacedMessage.sameMucUser(message); //can not be checked when using mam
+                    final boolean mucUserMatches =
+                            query == null
+                                    && replacedMessage.sameMucUser(
+                                            message); // can not be checked when using mam
                     final boolean duplicate = conversation.hasDuplicateMessage(message);
                     if (fingerprintsMatch && (trueCountersMatch || occupantIdMatch || !conversationMultiMode || mucUserMatches || counterpart.isBareJid()) && !duplicate) {
                         synchronized (replacedMessage) {
@@ -921,21 +1122,35 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                             mXmppConnectionService.updateMessage(replacedMessage, uuid);
                             if (mXmppConnectionService.confirmMessages()
                                     && replacedMessage.getStatus() == Message.STATUS_RECEIVED
-                                    && (replacedMessage.trusted() || replacedMessage.isPrivateMessage()) //TODO do we really want to send receipts for all PMs?
+                                    && (replacedMessage.trusted()
+                                            || replacedMessage
+                                                    .isPrivateMessage()) // TODO do we really want
+                                    // to send receipts for all
+                                    // PMs?
                                     && remoteMsgId != null
                                     && !selfAddressed
                                     && !isTypeGroupChat) {
                                 processMessageReceipts(account, packet, remoteMsgId, query);
                             }
                             if (replacedMessage.getEncryption() == Message.ENCRYPTION_PGP) {
-                                conversation.getAccount().getPgpDecryptionService().discard(replacedMessage);
-                                conversation.getAccount().getPgpDecryptionService().decrypt(replacedMessage, false);
+                                conversation
+                                        .getAccount()
+                                        .getPgpDecryptionService()
+                                        .discard(replacedMessage);
+                                conversation
+                                        .getAccount()
+                                        .getPgpDecryptionService()
+                                        .decrypt(replacedMessage, false);
                             }
                         }
                         mXmppConnectionService.getNotificationService().updateNotification();
                         return;
                     } else {
-                        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received message correction but verification didn't check out");
+                        Log.d(
+                                Config.LOGTAG,
+                                account.getJid().asBareJid()
+                                        + ": received message correction but verification didn't"
+                                        + " check out");
                     }
                 } else if (message.getBody() == null || message.getBody().equals("") || message.getBody().equals(" ")) {
                     return;
@@ -943,10 +1158,14 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                 if (replaceElement != null && !replaceElement.getName().equals("replace")) return;
             }
 
-            boolean checkForDuplicates = (isTypeGroupChat && packet.hasChild("delay", "urn:xmpp:delay"))
-                    || message.isPrivateMessage()
-                    || message.getServerMsgId() != null
-                    || (query == null && mXmppConnectionService.getMessageArchiveService().isCatchupInProgress(conversation));
+            boolean checkForDuplicates =
+                    (isTypeGroupChat && packet.hasChild("delay", "urn:xmpp:delay"))
+                            || message.isPrivateMessage()
+                            || message.getServerMsgId() != null
+                            || (query == null
+                                    && mXmppConnectionService
+                                            .getMessageArchiveService()
+                                            .isCatchupInProgress(conversation));
             if (checkForDuplicates) {
                 final Message duplicate = conversation.findDuplicateMessage(message);
                 if (duplicate != null) {
@@ -956,7 +1175,8 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                             && duplicate.getServerMsgId() == null
                             && message.getServerMsgId() != null) {
                         duplicate.setServerMsgId(message.getServerMsgId());
-                        if (mXmppConnectionService.databaseBackend.updateMessage(duplicate, false)) {
+                        if (mXmppConnectionService.databaseBackend.updateMessage(
+                                duplicate, false)) {
                             serverMsgIdUpdated = true;
                         } else {
                             serverMsgIdUpdated = false;
@@ -965,12 +1185,18 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                     } else {
                         serverMsgIdUpdated = false;
                     }
-                    Log.d(Config.LOGTAG, "skipping duplicate message with " + message.getCounterpart() + ". serverMsgIdUpdated=" + serverMsgIdUpdated);
+                    Log.d(
+                            Config.LOGTAG,
+                            "skipping duplicate message with "
+                                    + message.getCounterpart()
+                                    + ". serverMsgIdUpdated="
+                                    + serverMsgIdUpdated);
                     return;
                 }
             }
 
-            if (query != null && query.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
+            if (query != null
+                    && query.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
                 conversation.prepend(query.getActualInThisQuery(), message);
             } else {
                 conversation.add(message);
@@ -979,7 +1205,7 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                 query.incrementActualMessageCount();
             }
 
-            if (query == null || query.isCatchup()) { //either no mam or catchup
+            if (query == null || query.isCatchup()) { // either no mam or catchup
                 if (status == Message.STATUS_SEND || status == Message.STATUS_SEND_RECEIVED) {
                     mXmppConnectionService.markRead(conversation);
                     if (query == null) {
@@ -992,13 +1218,21 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
             }
 
             if (message.getEncryption() == Message.ENCRYPTION_PGP) {
-                notify = conversation.getAccount().getPgpDecryptionService().decrypt(message, notify);
-            } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE || message.getEncryption() == Message.ENCRYPTION_AXOLOTL_FAILED) {
+                notify =
+                        conversation
+                                .getAccount()
+                                .getPgpDecryptionService()
+                                .decrypt(message, notify);
+            } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE
+                    || message.getEncryption() == Message.ENCRYPTION_AXOLOTL_FAILED) {
                 notify = false;
             }
 
             if (query == null) {
-                extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet);
+                extractChatState(
+                        mXmppConnectionService.find(account, counterpart.asBareJid()),
+                        isTypeGroupChat,
+                        packet);
                 mXmppConnectionService.updateConversationUi();
             }
 
@@ -1023,9 +1257,9 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
             }
 
             mXmppConnectionService.databaseBackend.createMessage(message);
-
-            final HttpConnectionManager manager = this.mXmppConnectionService.getHttpConnectionManager();
-            if (message.getRelativeFilePath() == null && message.trusted() && message.treatAsDownloadable() && manager.getAutoAcceptFileSize() > 0) {
+            final HttpConnectionManager manager =
+                    this.mXmppConnectionService.getHttpConnectionManager();
+            if (message.trusted() && message.treatAsDownloadable() && manager.getAutoAcceptFileSize() > 0) {
                 if (message.getOob() != null && "cid".equalsIgnoreCase(message.getOob().getScheme())) {
                     try {
                         BobTransfer transfer = new BobTransfer.ForMessage(message, mXmppConnectionService);
@@ -1044,16 +1278,19 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                     mXmppConnectionService.getNotificationService().push(message);
                 }
             }
-        } else if (!packet.hasChild("body")) { //no body
-
-            final Conversation conversation = mXmppConnectionService.find(account, from.asBareJid());
+        } else if (!packet.hasChild("body")) { // no body
+            final Conversation conversation =
+                    mXmppConnectionService.find(account, from.asBareJid());
             if (axolotlEncrypted != null) {
                 Jid origin;
                 if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
-                    final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
+                    final Jid fallback =
+                            conversation.getMucOptions().getTrueCounterpart(counterpart);
                     origin = getTrueCounterpart(query != null ? mucUserElement : null, fallback);
                     if (origin == null) {
-                        Log.d(Config.LOGTAG, "omemo key transport message in anonymous conference received");
+                        Log.d(
+                                Config.LOGTAG,
+                                "omemo key transport message in anonymous conference received");
                         return;
                     }
                 } else if (isTypeGroupChat) {
@@ -1062,25 +1299,43 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                     origin = from;
                 }
                 try {
-                    final XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlEncrypted, origin.asBareJid());
-                    account.getAxolotlService().processReceivingKeyTransportMessage(xmppAxolotlMessage, query != null);
-                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": omemo key transport message received from " + origin);
+                    final XmppAxolotlMessage xmppAxolotlMessage =
+                            XmppAxolotlMessage.fromElement(axolotlEncrypted, origin.asBareJid());
+                    account.getAxolotlService()
+                            .processReceivingKeyTransportMessage(xmppAxolotlMessage, query != null);
+                    Log.d(
+                            Config.LOGTAG,
+                            account.getJid().asBareJid()
+                                    + ": omemo key transport message received from "
+                                    + origin);
                 } catch (Exception e) {
-                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": invalid omemo key transport message received " + e.getMessage());
+                    Log.d(
+                            Config.LOGTAG,
+                            account.getJid().asBareJid()
+                                    + ": invalid omemo key transport message received "
+                                    + e.getMessage());
                     return;
                 }
             }
 
-            if (query == null && extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet)) {
+            if (query == null
+                    && extractChatState(
+                            mXmppConnectionService.find(account, counterpart.asBareJid()),
+                            isTypeGroupChat,
+                            packet)) {
                 mXmppConnectionService.updateConversationUi();
             }
 
             if (isTypeGroupChat) {
-                if (packet.hasChild("subject") && !packet.hasChild("thread")) { // We already know it has no body per above
+                if (packet.hasChild("subject")
+                        && !packet.hasChild("thread")) { // We already know it has no body per above
                     if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
                         conversation.setHasMessagesLeftOnServer(conversation.countMessages() > 0);
-                        final LocalizedContent subject = packet.findInternationalizedChildContentInDefaultNamespace("subject");
-                        if (subject != null && conversation.getMucOptions().setSubject(subject.content)) {
+                        final LocalizedContent subject =
+                                packet.findInternationalizedChildContentInDefaultNamespace(
+                                        "subject");
+                        if (subject != null
+                                && conversation.getMucOptions().setSubject(subject.content)) {
                             mXmppConnectionService.updateConversation(conversation);
                         }
                         mXmppConnectionService.updateConversationUi();
@@ -1088,7 +1343,10 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                     }
                 }
             }
-            if (conversation != null && mucUserElement != null && InvalidJid.hasValidFrom(packet) && from.isBareJid()) {
+            if (conversation != null
+                    && mucUserElement != null
+                    && Jid.Invalid.hasValidFrom(packet)
+                    && from.isBareJid()) {
                 for (Element child : mucUserElement.getChildren()) {
                     if ("status".equals(child.getName())) {
                         try {
@@ -1098,16 +1356,27 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                                 break;
                             }
                         } catch (Exception e) {
-                            //ignored
+                            // ignored
                         }
                     } else if ("item".equals(child.getName())) {
-                        MucOptions.User user = AbstractParser.parseItem(conversation, child);
-                        Log.d(Config.LOGTAG, account.getJid() + ": changing affiliation for "
-                                + user.getRealJid() + " to " + user.getAffiliation() + " in "
-                                + conversation.getJid().asBareJid());
+                        final var user = AbstractParser.parseItem(conversation, child);
+                        Log.d(
+                                Config.LOGTAG,
+                                account.getJid()
+                                        + ": changing affiliation for "
+                                        + user.getRealJid()
+                                        + " to "
+                                        + user.getAffiliation()
+                                        + " in "
+                                        + conversation.getJid().asBareJid());
                         if (!user.realJidMatchesAccount()) {
-                            boolean isNew = conversation.getMucOptions().updateUser(user);
-                            mXmppConnectionService.getAvatarService().clear(conversation);
+                            final var mucOptions = conversation.getMucOptions();
+                            final boolean isNew = mucOptions.updateUser(user);
+                            final var avatarService = mXmppConnectionService.getAvatarService();
+                            if (Strings.isNullOrEmpty(mucOptions.getAvatar())) {
+                                avatarService.clear(mucOptions);
+                            }
+                            avatarService.clear(user);
                             mXmppConnectionService.updateMucRosterUi();
                             mXmppConnectionService.updateConversationUi();
                             Contact contact = user.getContact();
@@ -1115,7 +1384,13 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                                 Jid jid = user.getRealJid();
                                 List<Jid> cryptoTargets = conversation.getAcceptedCryptoTargets();
                                 if (cryptoTargets.remove(user.getRealJid())) {
-                                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": removed " + jid + " from crypto targets of " + conversation.getName());
+                                    Log.d(
+                                            Config.LOGTAG,
+                                            account.getJid().asBareJid()
+                                                    + ": removed "
+                                                    + jid
+                                                    + " from crypto targets of "
+                                                    + conversation.getName());
                                     conversation.setAcceptedCryptoTargets(cryptoTargets);
                                     mXmppConnectionService.updateConversation(conversation);
                                 }

src/main/java/eu/siacs/conversations/parser/PresenceParser.java 🔗

@@ -1,7 +1,7 @@
 package eu.siacs.conversations.parser;
 
 import android.util.Log;
-
+import com.google.common.base.Strings;
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.crypto.PgpEngine;
 import eu.siacs.conversations.crypto.axolotl.AxolotlService;
@@ -17,48 +17,51 @@ 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
                         : mXmppConnectionService.find(account, packet.getFrom().asBareJid());
-        if (conversation != null) {
-            final MucOptions mucOptions = conversation.getMucOptions();
-            boolean before = mucOptions.online();
-            int count = mucOptions.getUserCount();
-            final List<MucOptions.User> tileUserBefore = mucOptions.getUsers(5);
-            processConferencePresence(packet, conversation);
-            final List<MucOptions.User> tileUserAfter = mucOptions.getUsers(5);
-            if (!tileUserAfter.equals(tileUserBefore)) {
-                mXmppConnectionService.getAvatarService().clear(mucOptions);
-            }
-            if (before != mucOptions.online()
-                    || (mucOptions.online() && count != mucOptions.getUserCount())) {
-                mXmppConnectionService.updateConversationUi();
-            } else if (mucOptions.online()) {
-                mXmppConnectionService.updateMucRosterUi();
-            }
+        if (conversation == null) {
+            return;
+        }
+        final MucOptions mucOptions = conversation.getMucOptions();
+        boolean before = mucOptions.online();
+        int count = mucOptions.getUserCount();
+        final List<MucOptions.User> tileUserBefore = mucOptions.getUsers(5);
+        processConferencePresence(packet, conversation);
+        final List<MucOptions.User> tileUserAfter = mucOptions.getUsers(5);
+        if (Strings.isNullOrEmpty(mucOptions.getAvatar())
+                && !tileUserAfter.equals(tileUserBefore)) {
+            mXmppConnectionService.getAvatarService().clear(mucOptions);
+        }
+        if (before != mucOptions.online()
+                || (mucOptions.online() && count != mucOptions.getUserCount())) {
+            mXmppConnectionService.updateConversationUi();
+        } else if (mucOptions.online()) {
+            mXmppConnectionService.updateMucRosterUi();
         }
     }
 
-    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();
@@ -82,12 +85,15 @@ public class PresenceParser extends AbstractParser implements Consumer<im.conver
                         mucOptions.setError(MucOptions.Error.NONE);
                         MucOptions.User user = parseItem(conversation, item, from, occupantIdEl, nick == null ? null : nick.getContent(), hats);
                         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);
@@ -97,7 +103,8 @@ public class PresenceParser extends AbstractParser implements Consumer<im.conver
                                 Log.d(Config.LOGTAG,"role or affiliation changed");
                                 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);
                         }
@@ -154,9 +161,11 @@ public class PresenceParser extends AbstractParser implements Consumer<im.conver
                                                     .getAccount()
                                                     .getRoster()
                                                     .getContact(user.getRealJid());
-                                    c.setAvatar(avatar);
-                                    mXmppConnectionService.syncRoster(conversation.getAccount());
-                                    mXmppConnectionService.getAvatarService().clear(c);
+                                    if (c.setAvatar(avatar)) {
+                                        mXmppConnectionService.syncRoster(
+                                                conversation.getAccount());
+                                        mXmppConnectionService.getAvatarService().clear(c);
+                                    }
                                     mXmppConnectionService.updateRosterUi(XmppConnectionService.UpdateRosterReason.AVATAR);
                                 }
                             } else if (mXmppConnectionService.isDataSaverDisabled()) {
@@ -172,7 +181,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) {
@@ -307,7 +316,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())) {
@@ -329,11 +340,12 @@ public class PresenceParser extends AbstractParser implements Consumer<im.conver
                         mXmppConnectionService.updateConversationUi();
                         mXmppConnectionService.updateAccountUi();
                     } else {
-                        contact.setAvatar(avatar);
-                        mXmppConnectionService.syncRoster(account);
-                        mXmppConnectionService.getAvatarService().clear(contact);
-                        mXmppConnectionService.updateConversationUi();
-                        mXmppConnectionService.updateRosterUi(XmppConnectionService.UpdateRosterReason.AVATAR);
+                        if (contact.setAvatar(avatar)) {
+                            mXmppConnectionService.syncRoster(account);
+                            mXmppConnectionService.getAvatarService().clear(contact);
+                            mXmppConnectionService.updateConversationUi();
+                            mXmppConnectionService.updateRosterUi(XmppConnectionService.UpdateRosterReason.AVATAR);
+                        }
                     }
                 } else if (mXmppConnectionService.isDataSaverDisabled()) {
                     mXmppConnectionService.fetchAvatar(account, avatar);

src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java 🔗

@@ -69,7 +69,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;
@@ -1340,7 +1339,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
                                         cursor.getString(cursor.getColumnIndex(Account.SERVER)),
                                         null)
                                 .getDomain()
-                                .toEscapedString();
+                                .toString();
             } catch (IllegalArgumentException ignored) {
                 Log.e(
                         Config.LOGTAG,
@@ -1647,7 +1646,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);
@@ -2032,7 +2031,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;
@@ -2065,7 +2064,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);
@@ -2073,6 +2072,28 @@ public class DatabaseBackend extends SQLiteOpenHelper {
         }
     }
 
+    public String findConversationUuid(final Jid account, final Jid jid) {
+        final SQLiteDatabase db = this.getReadableDatabase();
+        final String[] selectionArgs = {
+            account.getLocal(),
+            account.getDomain().toString(),
+            jid.asBareJid().toString() + "/%",
+            jid.asBareJid().toString()
+        };
+        try (final Cursor cursor =
+                db.rawQuery(
+                        "SELECT conversations.uuid FROM conversations JOIN accounts ON"
+                            + " conversations.accountUuid=accounts.uuid WHERE accounts.username=?"
+                            + " AND accounts.server=? AND (contactJid=? OR contactJid LIKE ?)",
+                        selectionArgs)) {
+            if (cursor.getCount() == 0) {
+                return null;
+            }
+            cursor.moveToFirst();
+            return cursor.getString(0);
+        }
+    }
+
     public void updateConversation(final Conversation conversation) {
         final SQLiteDatabase db = this.getWritableDatabase();
         final String[] args = {conversation.getUuid()};

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;

src/main/java/eu/siacs/conversations/services/AvatarService.java 🔗

@@ -18,17 +18,10 @@ 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 com.google.common.base.Strings;
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.entities.Account;
@@ -46,6 +39,11 @@ 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 {
 
@@ -99,7 +97,7 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
 		if (conversation != null) {
 			return get(conversation,size,cacheOnly);
 		}
-		return get(CHANNEL_SYMBOL, room != null ? room.asBareJid().toEscapedString() : result.getName(), size, cacheOnly);
+		return get(CHANNEL_SYMBOL, room != null ? room.asBareJid().toString() : result.getName(), size, cacheOnly);
 	}
 
 	private Drawable get(final Contact contact, final int size, boolean cachedOnly) {
@@ -287,7 +285,7 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
 
 	public Drawable get(ListItem item, int size, boolean cachedOnly) {
 		if (item instanceof RawBlockable) {
-			return get(item.getDisplayName(), item.getJid().toEscapedString(), size, cachedOnly);
+			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) {
@@ -472,7 +470,7 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
 		avatar = mXmppConnectionService.getFileBackend().getAvatar(account.getAvatar(), size);
 		if (avatar == null) {
 			final String displayName = account.getDisplayName();
-			final String jid = account.getJid().asBareJid().toEscapedString();
+			final String jid = account.getJid().asBareJid().toString();
 			if (QuickConversationsService.isQuicksy() && !TextUtils.isEmpty(displayName)) {
 				avatar = get(displayName, jid, size, false);
 			} else {
@@ -551,7 +549,7 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
 	}
 
 	public static Drawable get(final Jid jid, final int size) {
-		return getImpl(jid.asBareJid().toEscapedString(), null, size);
+		return getImpl(jid.asBareJid().toString(), null, size);
 	}
 
 	private static Drawable getImpl(final String name, final String seed, final int size) {

src/main/java/eu/siacs/conversations/services/CallIntegration.java 🔗

@@ -460,7 +460,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() {

src/main/java/eu/siacs/conversations/services/CallIntegrationConnectionService.java 🔗

@@ -21,14 +21,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;
@@ -41,7 +38,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.ArrayList;
 import java.util.Collection;
@@ -93,7 +89,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"));
         }
@@ -109,8 +106,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;
@@ -137,7 +134,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
@@ -189,9 +187,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 =
@@ -228,7 +226,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);
@@ -367,7 +365,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");
             }

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());
         }
@@ -207,10 +200,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);
                                                 }
@@ -262,7 +253,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);
                     }

src/main/java/eu/siacs/conversations/services/NotificationService.java 🔗

@@ -639,9 +639,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);
@@ -797,9 +796,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,
@@ -2081,7 +2079,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));
@@ -2140,7 +2138,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(

src/main/java/eu/siacs/conversations/services/ShortcutService.java 🔗

@@ -14,10 +14,12 @@ import androidx.core.content.pm.ShortcutManagerCompat;
 import androidx.core.graphics.drawable.IconCompat;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Set;
 
+import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -32,11 +34,11 @@ import eu.siacs.conversations.ui.StartConversationActivity;
 import eu.siacs.conversations.ui.ConversationsActivity;
 import eu.siacs.conversations.utils.ReplacingSerialSingleThreadExecutor;
 import eu.siacs.conversations.xmpp.Jid;
-import java.util.Collection;
-import java.util.List;
 
 public class ShortcutService {
 
+    public static final char ID_SEPARATOR = '#';
+
     private final XmppConnectionService xmppConnectionService;
     private final ReplacingSerialSingleThreadExecutor replacingSerialSingleThreadExecutor =
             new ReplacingSerialSingleThreadExecutor(ShortcutService.class.getSimpleName());
@@ -167,17 +169,17 @@ public class ShortcutService {
     }
 
     private static String getShortcutId(final Contact contact) {
-        return contact.getAccount().getJid().asBareJid().toEscapedString()
-                + "#"
-                + contact.getJid().asBareJid().toEscapedString();
+        return Joiner.on(ID_SEPARATOR)
+                .join(
+                        contact.getAccount().getJid().asBareJid().toString(),
+                        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 Joiner.on(ID_SEPARATOR)
+                .join(account.getJid().asBareJid().toString(), jid.asBareJid().toString());
     }
 
     private Intent getShortcutIntent(final MucOptions mucOptions) {
@@ -187,17 +189,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) {

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

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -184,7 +184,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.OnGatewayResult;
@@ -2554,7 +2553,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;
                             }
@@ -2630,7 +2629,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;
         }
@@ -2791,7 +2790,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);
@@ -2811,7 +2810,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");
@@ -3577,10 +3576,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);
     }
 
@@ -4018,15 +4017,15 @@ public class XmppConnectionService extends Service {
     }
 
     public boolean checkListeners() {
-        return (this.mOnAccountUpdates.size() == 0
-                && this.mOnConversationUpdates.size() == 0
-                && this.mOnRosterUpdates.size() == 0
-                && this.mOnCaptchaRequested.size() == 0
-                && this.mOnMucRosterUpdate.size() == 0
-                && this.mOnUpdateBlocklist.size() == 0
-                && this.mOnShowErrorToasts.size() == 0
-                && this.onJingleRtpConnectionUpdate.size() == 0
-                && this.mOnKeyStatusUpdated.size() == 0);
+        return (this.mOnAccountUpdates.isEmpty()
+                && this.mOnConversationUpdates.isEmpty()
+                && this.mOnRosterUpdates.isEmpty()
+                && this.mOnCaptchaRequested.isEmpty()
+                && this.mOnMucRosterUpdate.isEmpty()
+                && this.mOnUpdateBlocklist.isEmpty()
+                && this.mOnShowErrorToasts.isEmpty()
+                && this.onJingleRtpConnectionUpdate.isEmpty()
+                && this.mOnKeyStatusUpdated.isEmpty());
     }
 
     private void switchToForeground() {
@@ -4383,7 +4382,8 @@ public class XmppConnectionService extends Service {
                         }
                         ++i;
                         if (i >= affiliations.size()) {
-                            List<Jid> members = conversation.getMucOptions().getMembers(true);
+                            final var mucOptions = conversation.getMucOptions();
+                            List<Jid> members = mucOptions.getMembers(true);
                             if (success) {
                                 List<Jid> cryptoTargets = conversation.getAcceptedCryptoTargets();
                                 boolean changed = false;
@@ -4408,7 +4408,7 @@ public class XmppConnectionService extends Service {
                                     updateConversation(conversation);
                                 }
                             }
-                            getAvatarService().clear(conversation);
+                            getAvatarService().clear(mucOptions);
                             updateMucRosterUi();
                             updateConversationUi();
                         }
@@ -5093,8 +5093,9 @@ public class XmppConnectionService extends Service {
                 request,
                 (response) -> {
                     if (response.getType() == Iq.Type.RESULT) {
-                        conference.getMucOptions().changeAffiliation(jid, affiliation);
-                        getAvatarService().clear(conference);
+                        final var mucOptions = conference.getMucOptions();
+                        mucOptions.changeAffiliation(jid, affiliation);
+                        getAvatarService().clear(mucOptions);
                         if (callback != null) {
                             callback.onAffiliationChangedSuccessful(jid);
                         } else {
@@ -6373,7 +6374,7 @@ public class XmppConnectionService extends Service {
                 account,
                 Namespace.MDS_DISPLAYED,
                 item,
-                itemId.toEscapedString(),
+                itemId.toString(),
                 PublishOptions.persistentWhitelistAccessMaxItems());
     }
 
@@ -6526,7 +6527,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) {
@@ -6543,7 +6544,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);
                     }

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);
+    }
 
-		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);
+    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().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,34 +70,44 @@ 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;
-				var finalServerId = serverMsgId;
-				if (serverMsgId == null && binding.reportSpam.isChecked() && blockable instanceof Conversation) {
-					final var lastM = ((Conversation) blockable).getLatestMessage();
-					if (lastM != null) finalServerId = lastM.getServerMsgId();
-				}
-
-				if (xmppActivity.xmppConnectionService.sendBlockRequest(blockable, binding.reportSpam.isChecked(), finalServerId)) {
-					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;
+                        var finalServerId = serverMsgId;
+                        if (serverMsgId == null && binding.reportSpam.isChecked() && blockable instanceof Conversation) {
+                            final var lastM = ((Conversation) blockable).getLatestMessage();
+                            if (lastM != null) finalServerId = lastM.getServerMsgId();
+                        }
+                        if (xmppActivity.xmppConnectionService.sendBlockRequest(
+                                blockable, binding.reportSpam.isChecked(), finalServerId)) {
+                            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();
+    }
 }

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,99 +13,109 @@ 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,
-				null,
-				account.getJid().asBareJid().toEscapedString(),
-				true,
-				false,
-				EnterJidDialog.SanityCheck.NO
-		);
+    @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, x, y) -> {
-			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,
+                        null,
+                        account.getJid().asBareJid().toString(),
+                        true,
+                        false,
+                        EnterJidDialog.SanityCheck.NO);
 
-		dialog.show(ft, "dialog");
-	}
+        dialog.setOnEnterJidDialogPositiveListener(
+                (accountJid, contactJid, x, y) -> {
+                    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();
+    }
 }

src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java 🔗

@@ -311,7 +311,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(

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

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";
@@ -76,11 +72,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;
     }
 
@@ -136,7 +132,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));
 
         getListItemAdapter().refreshSettings();
         getListItemAdapter().setOnTagClickedListener((tag) -> {
@@ -174,9 +174,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;
     }
@@ -241,11 +243,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;
     }
 
@@ -254,7 +259,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();
@@ -290,7 +296,7 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im
         }
         final var accounts = new ArrayList<Account>();
         for (final var account : xmppConnectionService.getAccounts()) {
-            if (mActivatedAccounts.contains(account.getJid().asBareJid().toEscapedString())) accounts.add(account);
+            if (mActivatedAccounts.contains(account.getJid().asBareJid().toString())) accounts.add(account);
         }
         for (final var contact : extraContacts) {
             if (!filterContacts.contains(contact.getJid().asBareJid().toString())
@@ -325,7 +331,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
@@ -380,8 +386,8 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im
                 }
             }
 
-            return true;
-        });
+                    return true;
+                });
 
         dialog.show(ft, "dialog");
     }
@@ -398,7 +404,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()) {
@@ -412,8 +419,8 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im
         this.mActivatedAccounts.clear();
         final var selected = getIntent().getStringExtra(EXTRA_ACCOUNT);
         for (final Account account : xmppConnectionService.getAccounts()) {
-            if (account.isEnabled() && (selected == null || selected.equals(account.getJid().asBareJid().toEscapedString()))) {
-                this.mActivatedAccounts.add(account.getJid().asBareJid().toEscapedString());
+            if (account.isEnabled() && (selected == null || selected.equals(account.getJid().asBareJid().toString()))) {
+                this.mActivatedAccounts.add(account.getJid().asBareJid().toString());
             }
         }
         filterContacts();
@@ -421,14 +428,16 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im
         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);
     }
@@ -456,8 +465,10 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im
 
             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);
     }
@@ -468,7 +479,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);

src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java 🔗

@@ -328,7 +328,7 @@ public class ConferenceDetailsActivity extends XmppActivity
                 });
         this.binding.relatedMucs.setOnClickListener(v -> {
             final Intent intent = new Intent(this, ChannelDiscoveryActivity.class);
-            intent.putExtra("services", new String[]{ mConversation.getJid().getDomain().toEscapedString(), mConversation.getAccount().getJid().toEscapedString() });
+            intent.putExtra("services", new String[]{ mConversation.getJid().getDomain().toString(), mConversation.getAccount().getJid().toString() });
             startActivity(intent);
         });
     }
@@ -518,10 +518,9 @@ 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:" + Uri.encode(mConversation.getJid().asBareJid().toEscapedString(), "@/+") + "?join";
+                return "xmpp:" + Uri.encode(mConversation.getJid().asBareJid().toString(), "@/+") + "?join";
             }
         } else {
             return null;
@@ -642,7 +641,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
@@ -655,10 +654,10 @@ public class ConferenceDetailsActivity extends XmppActivity
         if (mConversation.isPrivateAndNonAnonymous()) {
             this.binding.jid.setText(
                     getString(R.string.hosted_on, mConversation.getJid().getDomain()));
-            this.binding.truejid.setText(mConversation.getJid().asBareJid().toEscapedString());
+            this.binding.truejid.setText(mConversation.getJid().asBareJid().toString());
             if (mAdvancedMode) this.binding.truejid.setVisibility(View.VISIBLE);
         } 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);
@@ -843,7 +842,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

src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java 🔗

@@ -202,7 +202,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));
@@ -262,9 +262,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:" + Uri.encode(contact.getJid().asBareJid().toEscapedString(), "@/+");
+            return "xmpp:" + Uri.encode(contact.getJid().asBareJid().toString(), "@/+");
         }
     }
 
@@ -276,11 +276,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) {
             }
         }
@@ -370,7 +370,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();
@@ -598,7 +598,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);

src/main/java/eu/siacs/conversations/ui/ConversationFragment.java 🔗

@@ -1099,8 +1099,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;
@@ -2298,8 +2297,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);
@@ -4742,7 +4741,7 @@ public class ConversationFragment extends XmppFragment
                                                     + message.getContact()
                                                             .getJid()
                                                             .asBareJid()
-                                                            .toEscapedString());
+                                                            .toString());
                                     break;
                             }
                             return true;

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.ContextMenu;
@@ -47,23 +48,15 @@ import android.view.ViewGroup;
 import android.widget.AdapterView.AdapterContextMenuInfo;
 import android.widget.PopupMenu;
 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.base.Optional;
 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;
@@ -88,471 +81,543 @@ import eu.siacs.conversations.xmpp.jingle.OngoingRtpSession;
 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(activity.xmppConnectionService) && conversation.getMode() == Conversation.MODE_SINGLE) {
-						return;
-					}
-					activity.xmppConnectionService.archiveConversation(c);
-				}
-			});
-			snackbar.show();
-		}
-	};
-
-	private ItemTouchHelper touchHelper = null;
-
-	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) -> activity.launchStartConversation());
-
-		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));
-		registerForContextMenu(this.binding.list);
-		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) {
-		menuInflater.inflate(R.menu.fragment_conversations_overview, menu);
-		AccountUtils.showHideMenuItems(menu);
-		final MenuItem easyOnboardInvite = menu.findItem(R.id.action_easy_invite);
-		easyOnboardInvite.setVisible(EasyOnboardingInvite.anyHasSupport(activity == null ? null : activity.xmppConnectionService));
-		if (activity != null && activity.xmppConnectionService != null && activity.xmppConnectionService.isOnboarding()) {
-			final MenuItem manageAccounts = menu.findItem(R.id.action_accounts);
-			if (manageAccounts != null) manageAccounts.setVisible(false);
-
-			final MenuItem settings = menu.findItem(R.id.action_settings);
-			if (settings != null) settings.setVisible(false);
-		}
-	}
-
-	@Override
-	public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
-		activity.getMenuInflater().inflate(R.menu.conversations, menu);
-
-		final MenuItem menuMucDetails = menu.findItem(R.id.action_muc_details);
-		final MenuItem menuContactDetails = menu.findItem(R.id.action_contact_details);
-		final MenuItem menuMute = menu.findItem(R.id.action_mute);
-		final MenuItem menuUnmute = menu.findItem(R.id.action_unmute);
-		final MenuItem menuOngoingCall = menu.findItem(R.id.action_ongoing_call);
-		final MenuItem menuTogglePinned = menu.findItem(R.id.action_toggle_pinned);
-		final MenuItem menuArchiveChat = menu.findItem(R.id.action_archive);
-
-		if (menuInfo == null) return;
-		int pos = ((AdapterContextMenuInfo) menuInfo).position;
-		if (pos < 0) return;
-		Conversation conversation = conversations.get(pos);
-		if (conversation != null) {
-			if (conversation.getMode() == Conversation.MODE_MULTI) {
-				menuContactDetails.setVisible(false);
-				menuMucDetails.setTitle(conversation.getMucOptions().isPrivateAndNonAnonymous() ? R.string.action_muc_details : R.string.channel_details);
-				menuOngoingCall.setVisible(false);
-				menuArchiveChat.setTitle("Leave " + (conversation.getMucOptions().isPrivateAndNonAnonymous() ? "group chat" : "Channel"));
-			} else {
-				final XmppConnectionService service = activity == null ? null : activity.xmppConnectionService;
-				final Optional<OngoingRtpSession> ongoingRtpSession = service == null ? Optional.absent() : service.getJingleConnectionManager().getOngoingRtpConnection(conversation.getContact());
-				if (ongoingRtpSession.isPresent()) {
-					menuOngoingCall.setVisible(true);
-				} else {
-					menuOngoingCall.setVisible(false);
-				}
-				menuContactDetails.setVisible(!conversation.withSelf());
-				menuMucDetails.setVisible(false);
-				menuArchiveChat.setTitle(R.string.action_archive_chat);
-			}
-			if (conversation.isMuted()) {
-				menuMute.setVisible(false);
-			} else {
-				menuUnmute.setVisible(false);
-			}
-			if (conversation.getBooleanAttribute(Conversation.ATTRIBUTE_PINNED_ON_TOP, false)) {
-				menuTogglePinned.setTitle(R.string.remove_from_favorites);
-			} else {
-				menuTogglePinned.setTitle(R.string.add_to_favorites);
-			}
-		}
-		super.onCreateContextMenu(menu, view, menuInfo);
-	}
-
-	@Override
-	public boolean onContextItemSelected(MenuItem item) {
-		final var info = ((AdapterContextMenuInfo) item.getMenuInfo());
-		if (info == null) return false;
-
-		int pos = info.position;
-		if (conversations == null || conversations.size() <= pos || pos < 0) return false;
-
-		Conversation conversation = conversations.get(pos);
-		ConversationFragment fragment = new ConversationFragment();
-		fragment.setHasOptionsMenu(false);
-		fragment.onAttach(activity);
-		fragment.reInit(conversation, null);
-		boolean r = fragment.onOptionsItemSelected(item);
-		refresh();
-		return r;
-	}
-
-	@Override
-	public void onBackendConnected() {
-		refresh();
-	}
-
-	private void setupSwipe() {
-		if (this.touchHelper == null && (activity.xmppConnectionService == null || !activity.xmppConnectionService.isOnboarding())) {
-			this.touchHelper = new ItemTouchHelper(this.callback);
-			this.touchHelper.attachToRecyclerView(this.binding.list);
-		}
-	}
-
-	@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
-	protected void refresh() {
-		if (binding == null || this.activity == null) {
-			Log.d(Config.LOGTAG,"ConversationsOverviewFragment.refresh() skipped updated because view binding or activity was null");
-			return;
-		}
-		this.activity.populateWithOrderedConversations(this.conversations);
-		Conversation removed = this.swipedConversation.peek();
-		if (removed != null) {
-			if (removed.isRead(activity == null ? null : activity.xmppConnectionService)) {
-				this.conversations.remove(removed);
-			} else {
-				pendingActionHelper.execute();
-			}
-		}
-		this.conversationsAdapter.notifyDataSetChanged();
-		ScrollState scrollState = pendingScrollState.pop();
-		if (scrollState != null) {
-			setScrollPosition(scrollState);
-		}
-
-		if (activity.xmppConnectionService != null && activity.xmppConnectionService.isOnboarding()) {
-			binding.fab.setVisibility(View.GONE);
-
-			if (this.conversations.size() == 1) {
-				if (activity instanceof OnConversationSelected) {
-					((OnConversationSelected) activity).onConversationSelected(this.conversations.get(0));
-				} else {
-					Log.w(ConversationsOverviewFragment.class.getCanonicalName(), "Activity does not implement OnConversationSelected");
-				}
-			}
-		} else {
-			binding.fab.setVisibility(View.VISIBLE);
-		}
-		setupSwipe();
-
-		if (activity.xmppConnectionService == null || binding == null || binding.overviewSnackbar == null) return;
-		binding.overviewSnackbar.setVisibility(View.GONE);
-		for (final var account : activity.xmppConnectionService.getAccounts()) {
-			if (activity.getPreferences().getBoolean("no_mam_pref_warn:" + account.getUuid(), false)) continue;
-			if (account.mamPrefs() != null && !"always".equals(account.mamPrefs().getAttribute("default"))) {
-				binding.overviewSnackbar.setVisibility(View.VISIBLE);
-				binding.overviewSnackbarMessage.setText("Your account " + account.getJid().asBareJid().toEscapedString() + " does not have archiving fully enabled. This may result in missed messages if you use multiple devices or apps.");
-				binding.overviewSnackbarAction.setOnClickListener((v) -> {
-					final var prefs = account.mamPrefs();
-					prefs.setAttribute("default", "always");
-					activity.xmppConnectionService.pushMamPreferences(account, prefs);
-					refresh();
-				});
-
-				binding.overviewSnackbarAction.setOnLongClickListener((v) -> {
-					PopupMenu popupMenu = new PopupMenu(getActivity(), v);
-					popupMenu.inflate(R.menu.mam_pref_fix);
-					popupMenu.setOnMenuItemClickListener(menuItem -> {
-							switch (menuItem.getItemId()) {
-							case R.id.ignore:
-								final var editor = activity.getPreferences().edit();
-								editor.putBoolean("no_mam_pref_warn:" + account.getUuid(), true).apply();
-								editor.apply();
-								refresh();
-								return true;
-							}
-							return true;
-					});
-					popupMenu.show();
-					return true;
-				});
-				break;
-			}
-		}
-	}
-
-	private void setScrollPosition(ScrollState scrollPosition) {
-		if (scrollPosition != null) {
-			LinearLayoutManager layoutManager = (LinearLayoutManager) binding.list.getLayoutManager();
-			layoutManager.scrollToPositionWithOffset(scrollPosition.position, scrollPosition.offset);
-		}
-	}
+    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(activity.xmppConnectionService)
+                                            && 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));
+          registerForContextMenu(this.binding.list);
+        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) {
+        menuInflater.inflate(R.menu.fragment_conversations_overview, menu);
+        AccountUtils.showHideMenuItems(menu);
+        final MenuItem easyOnboardInvite = menu.findItem(R.id.action_easy_invite);
+        easyOnboardInvite.setVisible(EasyOnboardingInvite.anyHasSupport(activity == null ? null : activity.xmppConnectionService));
+        if (activity != null && activity.xmppConnectionService != null && activity.xmppConnectionService.isOnboarding()) {
+            final MenuItem manageAccounts = menu.findItem(R.id.action_accounts);
+            if (manageAccounts != null) manageAccounts.setVisible(false);
+
+            final MenuItem settings = menu.findItem(R.id.action_settings);
+            if (settings != null) settings.setVisible(false);
+        }
+    }
+
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
+        activity.getMenuInflater().inflate(R.menu.conversations, menu);
+
+        final MenuItem menuMucDetails = menu.findItem(R.id.action_muc_details);
+        final MenuItem menuContactDetails = menu.findItem(R.id.action_contact_details);
+        final MenuItem menuMute = menu.findItem(R.id.action_mute);
+        final MenuItem menuUnmute = menu.findItem(R.id.action_unmute);
+        final MenuItem menuOngoingCall = menu.findItem(R.id.action_ongoing_call);
+        final MenuItem menuTogglePinned = menu.findItem(R.id.action_toggle_pinned);
+        final MenuItem menuArchiveChat = menu.findItem(R.id.action_archive);
+
+        if (menuInfo == null) return;
+        int pos = ((AdapterContextMenuInfo) menuInfo).position;
+        if (pos < 0) return;
+        Conversation conversation = conversations.get(pos);
+        if (conversation != null) {
+            if (conversation.getMode() == Conversation.MODE_MULTI) {
+                menuContactDetails.setVisible(false);
+                menuMucDetails.setTitle(conversation.getMucOptions().isPrivateAndNonAnonymous() ? R.string.action_muc_details : R.string.channel_details);
+                menuOngoingCall.setVisible(false);
+                menuArchiveChat.setTitle("Leave " + (conversation.getMucOptions().isPrivateAndNonAnonymous() ? "group chat" : "Channel"));
+            } else {
+                final XmppConnectionService service = activity == null ? null : activity.xmppConnectionService;
+                final Optional<OngoingRtpSession> ongoingRtpSession = service == null ? Optional.absent() : service.getJingleConnectionManager().getOngoingRtpConnection(conversation.getContact());
+                if (ongoingRtpSession.isPresent()) {
+                    menuOngoingCall.setVisible(true);
+                } else {
+                    menuOngoingCall.setVisible(false);
+                }
+                menuContactDetails.setVisible(!conversation.withSelf());
+                menuMucDetails.setVisible(false);
+                menuArchiveChat.setTitle(R.string.action_archive_chat);
+            }
+            if (conversation.isMuted()) {
+                menuMute.setVisible(false);
+            } else {
+                menuUnmute.setVisible(false);
+            }
+            if (conversation.getBooleanAttribute(Conversation.ATTRIBUTE_PINNED_ON_TOP, false)) {
+                menuTogglePinned.setTitle(R.string.remove_from_favorites);
+            } else {
+                menuTogglePinned.setTitle(R.string.add_to_favorites);
+            }
+        }
+        super.onCreateContextMenu(menu, view, menuInfo);
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        final var info = ((AdapterContextMenuInfo) item.getMenuInfo());
+        if (info == null) return false;
+
+        int pos = info.position;
+        if (conversations == null || conversations.size() <= pos || pos < 0) return false;
+
+        Conversation conversation = conversations.get(pos);
+        ConversationFragment fragment = new ConversationFragment();
+        fragment.setHasOptionsMenu(false);
+        fragment.onAttach(activity);
+        fragment.reInit(conversation, null);
+        boolean r = fragment.onOptionsItemSelected(item);
+        refresh();
+        return r;
+    }
+
+    @Override
+    public void onBackendConnected() {
+        refresh();
+    }
+
+    private void setupSwipe() {
+        if (this.touchHelper == null && (activity.xmppConnectionService == null || !activity.xmppConnectionService.isOnboarding())) {
+            this.touchHelper = new ItemTouchHelper(this.callback);
+            this.touchHelper.attachToRecyclerView(this.binding.list);
+        }
+    }
+
+    @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
+    protected void refresh() {
+        if (binding == null || this.activity == null) {
+            Log.d(Config.LOGTAG,"ConversationsOverviewFragment.refresh() skipped updated because view binding or activity was null");
+            return;
+        }
+        this.activity.populateWithOrderedConversations(this.conversations);
+        Conversation removed = this.swipedConversation.peek();
+        if (removed != null) {
+            if (removed.isRead(activity == null ? null : activity.xmppConnectionService)) {
+                this.conversations.remove(removed);
+            } else {
+                pendingActionHelper.execute();
+            }
+        }
+        this.conversationsAdapter.notifyDataSetChanged();
+        ScrollState scrollState = pendingScrollState.pop();
+        if (scrollState != null) {
+            setScrollPosition(scrollState);
+        }
+
+        if (activity.xmppConnectionService != null && activity.xmppConnectionService.isOnboarding()) {
+            binding.fab.setVisibility(View.GONE);
+
+            if (this.conversations.size() == 1) {
+                if (activity instanceof OnConversationSelected) {
+                    ((OnConversationSelected) activity).onConversationSelected(this.conversations.get(0));
+                } else {
+                    Log.w(ConversationsOverviewFragment.class.getCanonicalName(), "Activity does not implement OnConversationSelected");
+                }
+            }
+        } else {
+            binding.fab.setVisibility(View.VISIBLE);
+        }
+        setupSwipe();
+
+        if (activity.xmppConnectionService == null || binding == null || binding.overviewSnackbar == null) return;
+        binding.overviewSnackbar.setVisibility(View.GONE);
+        for (final var account : activity.xmppConnectionService.getAccounts()) {
+            if (activity.getPreferences().getBoolean("no_mam_pref_warn:" + account.getUuid(), false)) continue;
+            if (account.mamPrefs() != null && !"always".equals(account.mamPrefs().getAttribute("default"))) {
+                binding.overviewSnackbar.setVisibility(View.VISIBLE);
+                binding.overviewSnackbarMessage.setText("Your account " + account.getJid().asBareJid().toString() + " does not have archiving fully enabled. This may result in missed messages if you use multiple devices or apps.");
+                binding.overviewSnackbarAction.setOnClickListener((v) -> {
+                    final var prefs = account.mamPrefs();
+                    prefs.setAttribute("default", "always");
+                    activity.xmppConnectionService.pushMamPreferences(account, prefs);
+                    refresh();
+                });
+
+                binding.overviewSnackbarAction.setOnLongClickListener((v) -> {
+                    PopupMenu popupMenu = new PopupMenu(getActivity(), v);
+                    popupMenu.inflate(R.menu.mam_pref_fix);
+                    popupMenu.setOnMenuItemClickListener(menuItem -> {
+                            switch (menuItem.getItemId()) {
+                            case R.id.ignore:
+                                final var editor = activity.getPreferences().edit();
+                                editor.putBoolean("no_mam_pref_warn:" + account.getUuid(), true).apply();
+                                editor.apply();
+                                refresh();
+                                return true;
+                            }
+                            return true;
+                    });
+                    popupMenu.show();
+                    return true;
+                });
+                break;
+            }
+        }
+    }
+
+    private void setScrollPosition(ScrollState scrollPosition) {
+        if (scrollPosition != null) {
+            LinearLayoutManager layoutManager =
+                    (LinearLayoutManager) binding.list.getLayoutManager();
+            layoutManager.scrollToPositionWithOffset(
+                    scrollPosition.position, scrollPosition.offset);
+        }
+    }
 }

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

src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java 🔗

@@ -32,13 +32,11 @@ 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;
@@ -100,13 +98,9 @@ import static eu.siacs.conversations.utils.PermissionUtils.allGranted;
 import static eu.siacs.conversations.utils.PermissionUtils.writeGranted;
 
 import okhttp3.HttpUrl;
-
+import okhttp3.HttpUrl;
+import org.openintents.openpgp.util.OpenPgpUtils;
 import org.openintents.openpgp.util.OpenPgpUtils;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
 
 public class EditAccountActivity extends OmemoActivity
         implements OnAccountUpdate,
@@ -165,8 +159,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);
                     }
                 }
@@ -285,12 +278,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) {
@@ -564,14 +557,13 @@ public class EditAccountActivity extends OmemoActivity
                                         getApplicationContext(), StartConversationActivity.class);
                         intent.putExtra("init", true);
                         intent.putExtra(
-                                EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toEscapedString());
+                                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) {
@@ -731,9 +723,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());
     }
@@ -890,7 +882,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;
             }
@@ -990,8 +982,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);
@@ -1004,8 +995,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) {
@@ -1071,7 +1061,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:
@@ -1146,7 +1136,7 @@ public class EditAccountActivity extends OmemoActivity
 
     private void openChangePassword(boolean didUnlock) {
         final Intent changePasswordIntent = new Intent(this, ChangePasswordActivity.class);
-        changePasswordIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toEscapedString());
+        changePasswordIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString());
         changePasswordIntent.putExtra("did_unlock", didUnlock);
         startActivity(changePasswordIntent);
     }
@@ -1264,15 +1254,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());

src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java 🔗

@@ -129,10 +129,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);
@@ -162,7 +167,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);
@@ -229,7 +235,7 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected
 
     protected Jid accountJid() {
         try {
-            return Jid.ofEscaped((String) binding.account.getEditableText().toString());
+            return Jid.of((String) binding.account.getEditableText().toString());
         } catch (final IllegalArgumentException e) {
             return null;
         }
@@ -256,7 +262,7 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected
 
                 Jid contactJid = null;
                 try {
-                    contactJid = Jid.ofEscaped(jidString);
+                    contactJid = Jid.of(jidString);
                 } catch (final IllegalArgumentException e) {
                     binding.jidLayout.setError(getActivity().getString(R.string.invalid_jid));
                     return;
@@ -269,7 +275,7 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected
                         issuedWarning = true;
                         return;
                     }
-                    if (sanityCheckJid != SanityCheck.ALLOW_MUC && suspiciousSubDomain(contactJid.getDomain().toEscapedString())) {
+                    if (sanityCheckJid != SanityCheck.ALLOW_MUC && 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);
                         issuedWarning = true;

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

src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java 🔗

@@ -64,8 +64,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(
@@ -119,8 +118,7 @@ public class PublishProfilePictureActivity extends XmppActivity
                         intent.putExtra("init", true);
                         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);
                     }

src/main/java/eu/siacs/conversations/ui/RecordingActivity.java 🔗

@@ -16,34 +16,29 @@ import android.util.Log;
 import android.view.View;
 import android.view.WindowManager;
 import android.widget.Toast;
-
-import androidx.appcompat.app.AppCompatActivity;
 import androidx.databinding.DataBindingUtil;
-
+import com.google.common.base.Stopwatch;
 import com.google.common.collect.ImmutableSet;
-
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.databinding.ActivityRecordingBinding;
+import eu.siacs.conversations.utils.TimeFrameUtils;
 import java.io.File;
 import java.lang.ref.WeakReference;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.Locale;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-import java.util.Set;
-
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.R;
-import eu.siacs.conversations.databinding.ActivityRecordingBinding;
-import eu.siacs.conversations.ui.util.SettingsUtils;
-import eu.siacs.conversations.utils.TimeFrameUtils;
 
 public class RecordingActivity extends BaseActivity implements View.OnClickListener {
 
     private ActivityRecordingBinding binding;
 
     private MediaRecorder mRecorder;
-    private long mStartTime = 0;
+    private Stopwatch stopwatch;
 
     private final CountDownLatch outputFileWrittenLatch = new CountDownLatch(1);
 
@@ -65,17 +60,48 @@ public class RecordingActivity extends BaseActivity implements View.OnClickListe
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         this.binding = DataBindingUtil.setContentView(this, R.layout.activity_recording);
+        this.binding.timer.setOnClickListener(
+                v -> {
+                    onPauseContinue();
+                });
         this.binding.cancelButton.setOnClickListener(this);
         this.binding.shareButton.setOnClickListener(this);
         this.setFinishOnTouchOutside(false);
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
     }
+
+    private void onPauseContinue() {
+        final var recorder = this.mRecorder;
+        final var stopwatch = this.stopwatch;
+        if (recorder == null
+                || stopwatch == null
+                || Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+            return;
+        }
+        if (stopwatch.isRunning()) {
+            try {
+                recorder.pause();
+                stopwatch.stop();
+            } catch (final IllegalStateException e) {
+                Log.d(Config.LOGTAG, "could not pause recording", e);
+            }
+        } else {
+            try {
+                recorder.resume();
+                stopwatch.start();
+            } catch (final IllegalStateException e) {
+                Log.d(Config.LOGTAG, "could not resume recording", e);
+            }
+        }
+    }
+
     @Override
     public void onStart() {
         super.onStart();
         if (!startRecording()) {
             this.binding.shareButton.setEnabled(false);
-            this.binding.timer.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodyMedium);
+            this.binding.timer.setTextAppearance(
+                    com.google.android.material.R.style.TextAppearance_Material3_BodyMedium);
             // TODO reset font family. make red?
             this.binding.timer.setText(R.string.unable_to_start_recording);
         }
@@ -99,23 +125,32 @@ public class RecordingActivity extends BaseActivity implements View.OnClickListe
 
     private static final Set<String> AAC_SENSITIVE_DEVICES =
             new ImmutableSet.Builder<String>()
-                    .add("FP4")             // Fairphone 4 https://codeberg.org/monocles/monocles_chat/issues/133
-                    .add("ONEPLUS A6000")   // OnePlus 6 https://github.com/iNPUTmice/Conversations/issues/4329
-                    .add("ONEPLUS A6003")   // OnePlus 6 https://github.com/iNPUTmice/Conversations/issues/4329
-                    .add("ONEPLUS A6010")   // OnePlus 6T https://codeberg.org/monocles/monocles_chat/issues/133
-                    .add("ONEPLUS A6013")   // OnePlus 6T https://codeberg.org/monocles/monocles_chat/issues/133
-                    .add("Pixel 4a")        // Pixel 4a https://github.com/iNPUTmice/Conversations/issues/4223
-                    .add("WP12 Pro")        // Oukitel WP 12 Pro https://github.com/iNPUTmice/Conversations/issues/4223
-                    .add("Volla Phone X")   // Volla Phone X https://github.com/iNPUTmice/Conversations/issues/4223
+                    .add("FP4") // Fairphone 4
+                    // https://codeberg.org/monocles/monocles_chat/issues/133
+                    .add("ONEPLUS A6000") // OnePlus 6
+                    // https://github.com/iNPUTmice/Conversations/issues/4329
+                    .add("ONEPLUS A6003") // OnePlus 6
+                    // https://github.com/iNPUTmice/Conversations/issues/4329
+                    .add("ONEPLUS A6010") // OnePlus 6T
+                    // https://codeberg.org/monocles/monocles_chat/issues/133
+                    .add("ONEPLUS A6013") // OnePlus 6T
+                    // https://codeberg.org/monocles/monocles_chat/issues/133
+                    .add("Pixel 4a") // Pixel 4a
+                    // https://github.com/iNPUTmice/Conversations/issues/4223
+                    .add("WP12 Pro") // Oukitel WP 12 Pro
+                    // https://github.com/iNPUTmice/Conversations/issues/4223
+                    .add("Volla Phone X") // Volla Phone X
+                    // https://github.com/iNPUTmice/Conversations/issues/4223
                     .build();
 
     private boolean startRecording() {
         mRecorder = new MediaRecorder();
         final String userChosenCodec = getPreferences().getString("voice_message_codec", "");
+        stopwatch = Stopwatch.createUnstarted();
         try {
             mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
         } catch (final RuntimeException e) {
-            Log.e(Config.LOGTAG,"could not set audio source", e);
+            Log.e(Config.LOGTAG, "could not set audio source", e);
             return false;
         }
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
@@ -131,8 +166,10 @@ public class RecordingActivity extends BaseActivity implements View.OnClickListe
         } else if ("mpeg4".equals(userChosenCodec) || !Config.USE_OPUS_VOICE_MESSAGES) {
             outputFormat = MediaRecorder.OutputFormat.MPEG_4;
             mRecorder.setOutputFormat(outputFormat);
-            if (AAC_SENSITIVE_DEVICES.contains(Build.MODEL) && Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) {
-                // Changing these three settings for AAC sensitive devices for Android<=13 might lead to sporadically truncated (cut-off) voice messages.
+            if (AAC_SENSITIVE_DEVICES.contains(Build.MODEL)
+                    && Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) {
+                // Changing these three settings for AAC sensitive devices for Android<=13 might
+                // lead to sporadically truncated (cut-off) voice messages.
                 mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC);
                 mRecorder.setAudioSamplingRate(24_000);
                 mRecorder.setAudioEncodingBitRate(28_000);
@@ -154,7 +191,7 @@ public class RecordingActivity extends BaseActivity implements View.OnClickListe
         try {
             mRecorder.prepare();
             mRecorder.start();
-            mStartTime = SystemClock.elapsedRealtime();
+            stopwatch.start();
             mHandler.postDelayed(mTickExecutor, 100);
             Log.d(Config.LOGTAG, "started recording to " + mOutputFile.getAbsolutePath());
             return true;
@@ -168,14 +205,17 @@ public class RecordingActivity extends BaseActivity implements View.OnClickListe
         try {
             mRecorder.stop();
             mRecorder.release();
-        } catch (Exception e) {
+            if (stopwatch.isRunning()) {
+                stopwatch.stop();
+            }
+        } catch (final Exception e) {
+            Log.d(Config.LOGTAG, "could not save recording", e);
             if (saveFile) {
                 Toast.makeText(this, R.string.unable_to_save_recording, Toast.LENGTH_SHORT).show();
                 return;
             }
         } finally {
             mRecorder = null;
-            mStartTime = 0;
         }
         if (!saveFile && mOutputFile != null) {
             if (mOutputFile.delete()) {
@@ -272,24 +312,23 @@ public class RecordingActivity extends BaseActivity implements View.OnClickListe
     }
 
     private void tick() {
-        this.binding.timer.setText(TimeFrameUtils.formatTimePassed(mStartTime, true));
+        this.binding.timer.setText(
+                TimeFrameUtils.formatElapsedTime(stopwatch.elapsed(TimeUnit.MILLISECONDS), true));
     }
 
     @Override
     public void onClick(final View view) {
-        switch (view.getId()) {
-            case R.id.cancel_button:
-                mHandler.removeCallbacks(mTickExecutor);
-                stopRecording(false);
-                setResult(RESULT_CANCELED);
-                finish();
-                break;
-            case R.id.share_button:
-                this.binding.shareButton.setEnabled(false);
-                this.binding.shareButton.setText(R.string.please_wait);
-                mHandler.removeCallbacks(mTickExecutor);
-                mHandler.postDelayed(() -> stopRecording(true), 500);
-                break;
+        if (view.getId() == R.id.cancel_button) {
+            mHandler.removeCallbacks(mTickExecutor);
+            stopRecording(false);
+            setResult(RESULT_CANCELED);
+            finish();
+        } else if (view.getId() == R.id.share_button) {
+            this.binding.timer.setOnClickListener(null);
+            this.binding.shareButton.setEnabled(false);
+            this.binding.shareButton.setText(R.string.please_wait);
+            mHandler.removeCallbacks(mTickExecutor);
+            mHandler.postDelayed(() -> stopRecording(true), 500);
         }
     }
 }

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;
@@ -78,16 +75,6 @@ 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;
-
 public class RtpSessionActivity extends XmppActivity
         implements XmppConnectionService.OnJingleRtpConnectionUpdate,
                 eu.siacs.conversations.ui.widget.SurfaceViewRenderer.OnAspectRatioChanged {
@@ -350,7 +337,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
@@ -554,7 +541,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;
@@ -623,7 +610,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);
@@ -825,9 +812,9 @@ public class RtpSessionActivity extends XmppActivity
                             .getJingleConnectionManager()
                             .getTerminalSessionState(with, sessionId);
             if (terminatedRtpSession == null) {
-                Log.e(Config.LOGTAG, "failed to initialize activity with running rtp session. session not found");
-                finish();
-                return true;
+                throw new IllegalStateException(
+                        "failed to initialize activity with running rtp session. session not"
+                                + " found");
             }
             initializeWithTerminatedSessionState(account, with, terminatedRtpSession);
             return true;
@@ -888,8 +875,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);
     }
@@ -946,10 +933,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));
         }
     }
 
@@ -983,9 +972,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);
@@ -1481,12 +1468,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);
     }
 
@@ -1497,7 +1484,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);
@@ -1668,7 +1655,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);
@@ -1691,11 +1678,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(

src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java 🔗

@@ -12,12 +12,16 @@ import androidx.annotation.NonNull;
 import androidx.core.content.pm.ShortcutManagerCompat;
 import androidx.databinding.DataBindingUtil;
 import androidx.recyclerview.widget.LinearLayoutManager;
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.databinding.ActivityShareWithBinding;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.persistance.DatabaseBackend;
+import eu.siacs.conversations.services.ShortcutService;
 import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.ui.adapter.ConversationAdapter;
 import eu.siacs.conversations.xmpp.Jid;
@@ -132,10 +136,28 @@ public class ShareWithActivity extends XmppActivity
         if (shortcut.isPresent()) {
             final var extras = shortcut.get().getExtras();
             if (extras == null) {
-                return null;
+                return shortcutIdToConversationFallback(shortcutId);
             } else {
-                return extras.getString(ConversationsActivity.EXTRA_CONVERSATION);
+                final var conversation = extras.getString(ConversationsActivity.EXTRA_CONVERSATION);
+                if (Strings.isNullOrEmpty(conversation)) {
+                    return shortcutIdToConversationFallback(shortcutId);
+                } else {
+                    return conversation;
+                }
             }
+        } else {
+            return shortcutIdToConversationFallback(shortcutId);
+        }
+    }
+
+    private String shortcutIdToConversationFallback(final String shortcutId) {
+        final var parts =
+                Splitter.on(ShortcutService.ID_SEPARATOR).limit(2).splitToList(shortcutId);
+        if (parts.size() == 2) {
+            final var account = Jid.of(parts.get(0));
+            final var jid = Jid.of(parts.get(1));
+            final var database = DatabaseBackend.getInstance(getApplicationContext());
+            return database.findConversationUuid(account, jid);
         } else {
             return null;
         }
@@ -228,7 +250,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;
         }

src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java 🔗

@@ -38,7 +38,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;
@@ -66,7 +65,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;
@@ -289,7 +287,7 @@ public class StartConversationActivity extends XmppActivity
         if (account != null) {
             intent.putExtra(
                 EXTRA_ACCOUNT_FILTER,
-                account.getJid().asBareJid().toEscapedString());
+                account.getJid().asBareJid().toString());
         }
         if (q != null) {
             intent.putExtra(EXTRA_TEXT_FILTER, q);
@@ -410,7 +408,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;
                     }
@@ -466,14 +464,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;
         }
     }
@@ -555,12 +554,12 @@ public class StartConversationActivity extends XmppActivity
     }
 
     protected void shareBookmarkUri() {
-        shareAsChannel(this, contextItem.getJid().asBareJid().toEscapedString());
+        shareAsChannel(this, contextItem.getJid().asBareJid().toString());
     }
 
     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) {
@@ -600,7 +599,7 @@ public class StartConversationActivity extends XmppActivity
     }
 
     protected void showQrForContact() {
-        showQrCode("xmpp:" + contextItem.getJid().asBareJid().toEscapedString());
+        showQrCode("xmpp:" + contextItem.getJid().asBareJid().toString());
     }
 
     protected void toggleContactBlock() {
@@ -613,8 +612,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) -> {
@@ -636,11 +634,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,
@@ -787,7 +784,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;
             }
@@ -1160,7 +1157,7 @@ public class StartConversationActivity extends XmppActivity
                 if (onboardingAccount == null && account.getJid().getDomain().equals(Config.ONBOARDING_DOMAIN)) onboardingAccount = account;
 
                 if (accountJid != null) {
-                    if(account.getJid().asBareJid().toEscapedString().equals(accountJid)) {
+                    if(account.getJid().asBareJid().toString().equals(accountJid)) {
                         selectedAccount = account;
                     } else {
                         continue;
@@ -1251,11 +1248,11 @@ public class StartConversationActivity extends XmppActivity
                 switchToConversationDoNotAppend(muc, invite.getBody());
                 return true;
             } else {
-                showJoinConferenceDialog(invite.getJid().asBareJid().toEscapedString(), invite);
+                showJoinConferenceDialog(invite.getJid().asBareJid().toString(), invite);
                 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);
@@ -1279,10 +1276,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;
         }
@@ -1298,7 +1295,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(
@@ -1329,7 +1326,7 @@ public class StartConversationActivity extends XmppActivity
         ArrayList<ListItem.Tag> tags = new ArrayList<>();
         final var accounts = new ArrayList<Account>();
         for (final var account : xmppConnectionService.getAccounts()) {
-            if (mActivatedAccounts.contains(account.getJid().asBareJid().toEscapedString())) accounts.add(account);
+            if (mActivatedAccounts.contains(account.getJid().asBareJid().toString())) accounts.add(account);
         }
         boolean foundSopranica = false;
         for (final Account account : accounts) {
@@ -1454,8 +1451,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);
     }
@@ -1477,13 +1473,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));

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,433 +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() {
-		if (this.mAccount == null) {
-			return;
-		}
-
-		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));
+    }
 }

src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java 🔗

@@ -11,12 +11,10 @@ import android.preference.PreferenceManager;
 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 com.cheogram.android.DownloadDefaultStickers;
@@ -38,18 +36,12 @@ 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 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";
@@ -68,7 +60,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) {
@@ -178,7 +171,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;
                 }
@@ -227,7 +220,7 @@ public class UriHandlerActivity extends BaseActivity {
                     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);
@@ -258,8 +251,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
@@ -301,7 +294,7 @@ public class UriHandlerActivity extends BaseActivity {
     }
 
     private void showErrorOnUiThread(@StringRes int error) {
-        runOnUiThread(()-> showError(error));
+        runOnUiThread(() -> showError(error));
     }
 
     private static Class<?> findShareViaAccountClass() {

src/main/java/eu/siacs/conversations/ui/XmppActivity.java 🔗

@@ -838,8 +838,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);
     }
@@ -854,7 +854,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(
@@ -1251,7 +1251,7 @@ public abstract class XmppActivity extends ActionBarActivity {
         final AtomicReference<Account> selectedAccount = new AtomicReference<>(accounts.get(0));
         final MaterialAlertDialogBuilder alertDialogBuilder = new MaterialAlertDialogBuilder(this);
         alertDialogBuilder.setTitle(R.string.choose_account);
-        final String[] asStrings = Collections2.transform(accounts, a -> a.getJid().asBareJid().toEscapedString()).toArray(new String[0]);
+        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) -> showQrCode(selectedAccount.get().getShareableUri()));
@@ -1302,7 +1302,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;
         }

src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java 🔗

@@ -4,14 +4,10 @@ import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ArrayAdapter;
-
 import androidx.annotation.NonNull;
 import androidx.core.graphics.ColorUtils;
 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;
@@ -44,27 +40,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;
         }
         if (account.isOnlineAndConnected()) {
@@ -89,11 +100,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);
+                    }
+                });
         if (activity.xmppConnectionService != null && activity.xmppConnectionService.getAccounts().size() > 1) {
             viewHolder.binding.frame.setBackgroundColor(account.getColor(activity.isDark()));
         }
@@ -108,10 +120,7 @@ public class AccountAdapter extends ArrayAdapter<Account> {
         }
     }
 
-
-
     public interface OnTglAccountState {
         void onClickTglAccountState(Account account, boolean state);
     }
-
 }

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

src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java 🔗

@@ -10,17 +10,14 @@ import android.os.AsyncTask;
 import android.view.LayoutInflater;
 import android.view.ViewGroup;
 import android.widget.ImageView;
-
 import androidx.annotation.DimenRes;
 import androidx.annotation.DrawableRes;
 import androidx.annotation.NonNull;
 import androidx.core.widget.ImageViewCompat;
 import androidx.databinding.DataBindingUtil;
 import androidx.recyclerview.widget.RecyclerView;
-
 import com.google.android.material.color.MaterialColors;
 import com.google.common.base.Strings;
-
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.databinding.ItemMediaBinding;
 import eu.siacs.conversations.ui.XmppActivity;
@@ -45,6 +42,21 @@ public class MediaAdapter extends RecyclerView.Adapter<MediaAdapter.MediaViewHol
                     "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
                     "text/x-tex",
                     "text/plain");
+    public static final List<String> SPREAD_SHEET_MIMES =
+            Arrays.asList(
+                    "text/comma-separated-values",
+                    "application/vnd.ms-excel",
+                    "application/vnd.stardivision.calc",
+                    "application/vnd.oasis.opendocument.spreadsheet",
+                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+
+    public static final List<String> SLIDE_SHOW_MIMES =
+            Arrays.asList(
+                    "application/vnd.ms-powerpoint",
+                    "application/vnd.stardivision.impress",
+                    "application/vnd.oasis.opendocument.presentation",
+                    "application/vnd.openxmlformats-officedocument.presentationml.presentation",
+                    "application/vnd.openxmlformats-officedocument.presentationml.slideshow");
 
     private static final List<String> ARCHIVE_MIMES =
             Arrays.asList(
@@ -112,6 +124,10 @@ public class MediaAdapter extends RecyclerView.Adapter<MediaAdapter.MediaViewHol
             return R.drawable.ic_backup_48dp;
         } else if (DOCUMENT_MIMES.contains(mime)) {
             return R.drawable.ic_description_48dp;
+        } else if (SPREAD_SHEET_MIMES.contains(mime)) {
+            return R.drawable.ic_table_48dp;
+        } else if (SLIDE_SHOW_MIMES.contains(mime)) {
+            return R.drawable.ic_slideshow_48dp;
         } else if (mime.equals("application/gpx+xml")) {
             return R.drawable.ic_tour_48dp;
         } else if (mime.startsWith("image/")) {

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

src/main/java/eu/siacs/conversations/ui/service/AudioPlayer.java 🔗

@@ -14,16 +14,13 @@ import android.os.Handler;
 import android.os.PowerManager;
 import android.util.Log;
 import android.view.View;
-import android.widget.ImageButton;
 import android.widget.RelativeLayout;
 import android.widget.SeekBar;
 import android.widget.TextView;
-
 import androidx.core.app.ActivityCompat;
 import androidx.core.content.ContextCompat;
-
+import com.google.android.material.button.MaterialButton;
 import com.google.common.primitives.Ints;
-
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.entities.Message;
@@ -33,7 +30,6 @@ import eu.siacs.conversations.ui.adapter.MessageAdapter;
 import eu.siacs.conversations.ui.util.PendingItem;
 import eu.siacs.conversations.utils.TimeFrameUtils;
 import eu.siacs.conversations.utils.WeakReferenceSet;
-
 import java.lang.ref.WeakReference;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -54,7 +50,8 @@ public class AudioPlayer
     private final WeakReferenceSet<RelativeLayout> audioPlayerLayouts = new WeakReferenceSet<>();
     private final SensorManager sensorManager;
     private final Sensor proximitySensor;
-    private final PendingItem<WeakReference<ImageButton>> pendingOnClickView = new PendingItem<>();
+    private final PendingItem<WeakReference<MaterialButton>> pendingOnClickView =
+            new PendingItem<>();
 
     private final ExecutorService executor = Executors.newSingleThreadExecutor();
 
@@ -81,7 +78,7 @@ public class AudioPlayer
     }
 
     private static String formatTime(final int ms) {
-        return TimeFrameUtils.formatElapsedTime(ms,false);
+        return TimeFrameUtils.formatElapsedTime(ms, false);
     }
 
     private void initializeProximityWakeLock(Context context) {
@@ -124,20 +121,17 @@ public class AudioPlayer
         final Context context = viewHolder.playPause.getContext();
         if (message == currentlyPlayingMessage) {
             if (AudioPlayer.player != null && AudioPlayer.player.isPlaying()) {
-                viewHolder.playPause.setImageResource(R.drawable.ic_pause_24dp);
-                MessageAdapter.setImageTint(viewHolder.playPause, viewHolder.bubbleColor);
+                viewHolder.playPause.setIconResource(R.drawable.ic_pause_24dp);
                 viewHolder.playPause.setContentDescription(context.getString(R.string.pause_audio));
                 viewHolder.progress.setEnabled(true);
             } else {
                 viewHolder.playPause.setContentDescription(context.getString(R.string.play_audio));
-                viewHolder.playPause.setImageResource(R.drawable.ic_play_arrow_24dp);
-                MessageAdapter.setImageTint(viewHolder.playPause, viewHolder.bubbleColor);
+                viewHolder.playPause.setIconResource(R.drawable.ic_play_arrow_24dp);
                 viewHolder.progress.setEnabled(false);
             }
             return true;
         } else {
-            viewHolder.playPause.setImageResource(R.drawable.ic_play_arrow_24dp);
-            MessageAdapter.setImageTint(viewHolder.playPause, viewHolder.bubbleColor);
+            viewHolder.playPause.setIconResource(R.drawable.ic_play_arrow_24dp);
             viewHolder.playPause.setContentDescription(context.getString(R.string.play_audio));
             viewHolder.runtime.setText(formatTime(message.getFileParams().runtime));
             viewHolder.progress.setProgress(0);
@@ -150,12 +144,12 @@ public class AudioPlayer
     public synchronized void onClick(View v) {
         if (v.getId() == R.id.play_pause) {
             synchronized (LOCK) {
-                startStop((ImageButton) v);
+                startStop((MaterialButton) v);
             }
         }
     }
 
-    private void startStop(ImageButton playPause) {
+    private void startStop(final MaterialButton playPause) {
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU
                 && ContextCompat.checkSelfPermission(
                                 messageAdapter.getActivity(),
@@ -186,8 +180,7 @@ public class AudioPlayer
             player.pause();
             messageAdapter.flagScreenOff();
             releaseProximityWakeLock();
-            viewHolder.playPause.setImageResource(R.drawable.ic_play_arrow_24dp);
-            MessageAdapter.setImageTint(viewHolder.playPause, viewHolder.bubbleColor);
+            viewHolder.playPause.setIconResource(R.drawable.ic_play_arrow_24dp);
             viewHolder.playPause.setContentDescription(context.getString(R.string.play_audio));
         } else {
             viewHolder.progress.setEnabled(true);
@@ -195,8 +188,7 @@ public class AudioPlayer
             messageAdapter.flagScreenOn();
             acquireProximityWakeLock();
             this.stopRefresher(true);
-            viewHolder.playPause.setImageResource(R.drawable.ic_pause_24dp);
-            MessageAdapter.setImageTint(viewHolder.playPause, viewHolder.bubbleColor);
+            viewHolder.playPause.setIconResource(R.drawable.ic_pause_24dp);
             viewHolder.playPause.setContentDescription(context.getString(R.string.pause_audio));
         }
         return false;
@@ -222,8 +214,7 @@ public class AudioPlayer
             messageAdapter.flagScreenOn();
             acquireProximityWakeLock();
             viewHolder.progress.setEnabled(true);
-            viewHolder.playPause.setImageResource(R.drawable.ic_pause_24dp);
-            MessageAdapter.setImageTint(viewHolder.playPause, viewHolder.bubbleColor);
+            viewHolder.playPause.setIconResource(R.drawable.ic_pause_24dp);
             viewHolder.playPause.setContentDescription(
                     viewHolder.playPause.getContext().getString(R.string.pause_audio));
             sensorManager.registerListener(
@@ -239,9 +230,9 @@ public class AudioPlayer
     }
 
     public void startStopPending() {
-        WeakReference<ImageButton> reference = pendingOnClickView.pop();
+        final var reference = pendingOnClickView.pop();
         if (reference != null) {
-            ImageButton imageButton = reference.get();
+            var imageButton = reference.get();
             if (imageButton != null) {
                 startStop(imageButton);
             }
@@ -283,8 +274,7 @@ public class AudioPlayer
         final Message message = (Message) audioPlayer.getTag();
         viewHolder.playPause.setContentDescription(
                 viewHolder.playPause.getContext().getString(R.string.play_audio));
-        viewHolder.playPause.setImageResource(R.drawable.ic_play_arrow_24dp);
-        MessageAdapter.setImageTint(viewHolder.playPause, viewHolder.bubbleColor);
+        viewHolder.playPause.setIconResource(R.drawable.ic_play_arrow_24dp);
         if (message != null) {
             viewHolder.runtime.setText(formatTime(message.getFileParams().runtime));
         }
@@ -309,7 +299,8 @@ public class AudioPlayer
     }
 
     @Override
-    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+    public void onProgressChanged(
+            final SeekBar seekBar, final int progress, final boolean fromUser) {
         synchronized (AudioPlayer.LOCK) {
             final RelativeLayout audioPlayer = (RelativeLayout) seekBar.getParent();
             final Message message = (Message) audioPlayer.getTag();
@@ -462,7 +453,7 @@ public class AudioPlayer
     public static class ViewHolder {
         private TextView runtime;
         private SeekBar progress;
-        private ImageButton playPause;
+        private MaterialButton playPause;
         private MessageAdapter.BubbleColor bubbleColor = MessageAdapter.BubbleColor.SURFACE;
 
         public static ViewHolder get(final RelativeLayout audioPlayer) {

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;

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 = 1;
@@ -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 {}
 }

src/main/java/eu/siacs/conversations/utils/CryptoHelper.java 🔗

@@ -5,7 +5,6 @@ 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;
@@ -15,6 +14,11 @@ import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
 
 import java.io.InputStream;
 import java.io.IOException;
+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;
@@ -42,13 +46,15 @@ import eu.siacs.conversations.xmpp.Jid;
 
 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 = 25;
     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];
@@ -73,7 +79,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);
@@ -83,8 +92,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;
     }
@@ -100,9 +111,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++) {
@@ -154,7 +163,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);
@@ -177,7 +187,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) {
@@ -190,9 +203,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) {
@@ -214,26 +233,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()));
@@ -253,7 +279,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) {
@@ -273,8 +299,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;
         };
@@ -285,7 +312,9 @@ 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");
     }
 
     public static String multihashAlgo(Multihash.Type type) throws NoSuchAlgorithmException {

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,226 +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]);
-				if (patternTuple.domain.size() > 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);
+        }
+    }
 }

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

src/main/java/eu/siacs/conversations/utils/MimeUtils.java 🔗

@@ -20,10 +20,11 @@ import android.database.Cursor;
 import android.net.Uri;
 import android.provider.OpenableColumns;
 import android.util.Log;
-
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
-
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.entities.Transferable;
+import eu.siacs.conversations.worker.ExportBackupWorker;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -35,21 +36,17 @@ import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.entities.Transferable;
-import eu.siacs.conversations.worker.ExportBackupWorker;
-
 /**
- * Utilities for dealing with MIME types.
- * Used to implement java.net.URLConnection and android.webkit.MimeTypeMap.
+ * Utilities for dealing with MIME types. Used to implement java.net.URLConnection and
+ * android.webkit.MimeTypeMap.
  */
 public final class MimeUtils {
 
-    public static final List<String> AMBIGUOUS_CONTAINER_FORMATS = ImmutableList.of(
-            "application/ogg",
-            "video/3gpp", // .3gp files can contain audio, video or both
-            "video/3gpp2"
-    );
+    public static final List<String> AMBIGUOUS_CONTAINER_FORMATS =
+            ImmutableList.of(
+                    "application/ogg",
+                    "video/3gpp", // .3gp files can contain audio, video or both
+                    "video/3gpp2");
 
     private static final Map<String, String> mimeTypeToExtensionMap = new HashMap<>();
     private static final Map<String, String> extensionToMimeTypeMap = new HashMap<>();
@@ -104,6 +101,7 @@ public final class MimeUtils {
         add("application/vnd.oasis.opendocument.text-master", "odm");
         add("application/vnd.oasis.opendocument.text-template", "ott");
         add("application/vnd.oasis.opendocument.text-web", "oth");
+        add("application/vnd.oasis.opendocument.presentation", "odp");
         add("application/vnd.google-earth.kml+xml", "kml");
         add("application/vnd.google-earth.kmz", "kmz");
         add("application/msword", "doc");
@@ -141,7 +139,7 @@ public final class MimeUtils {
         add("application/vnd.sun.xml.writer.global", "sxg");
         add("application/vnd.sun.xml.writer.template", "stw");
         add("application/vnd.visio", "vsd");
-        add("application/x-7z-compressed","7z");
+        add("application/x-7z-compressed", "7z");
         add("application/x-abiword", "abw");
         add("application/x-apple-diskimage", "dmg");
         add("application/x-bcpio", "bcpio");
@@ -331,8 +329,8 @@ public final class MimeUtils {
         add("image/x-xbitmap", "xbm");
         add("image/x-xpixmap", "xpm");
         add("image/x-xwindowdump", "xwd");
-        add("message/rfc822","eml");
-        add("message/rfc822","mime");
+        add("message/rfc822", "eml");
+        add("message/rfc822", "mime");
         add("model/iges", "igs");
         add("model/iges", "iges");
         add("model/mesh", "msh");
@@ -353,7 +351,7 @@ public final class MimeUtils {
         add("text/plain", "asc");
         add("text/plain", "text");
         add("text/plain", "diff");
-        add("text/plain", "po");     // reserve "pot" for vnd.ms-powerpoint
+        add("text/plain", "po"); // reserve "pot" for vnd.ms-powerpoint
         add("text/richtext", "rtx");
         add("text/rtf", "rtf");
         add("text/text", "phps");
@@ -424,7 +422,8 @@ public final class MimeUtils {
     }
 
     // mime types that are more reliant by path
-    private static final Collection<String> PATH_PRECEDENCE_MIME_TYPE = Arrays.asList("audio/x-m4b");
+    private static final Collection<String> PATH_PRECEDENCE_MIME_TYPE =
+            Arrays.asList("audio/x-m4b");
 
     private static void add(String mimeType, String extension) {
         // If we have an existing x -> y mapping, we do not want to
@@ -453,7 +452,10 @@ public final class MimeUtils {
             }
         }
         // Standard location?
-        File f = new File(System.getProperty("java.home"), "lib" + File.separator + "content-types.properties");
+        File f =
+                new File(
+                        System.getProperty("java.home"),
+                        "lib" + File.separator + "content-types.properties");
         if (f.exists()) {
             try {
                 return new FileInputStream(f);
@@ -464,9 +466,9 @@ public final class MimeUtils {
     }
 
     /**
-     * This isn't what the RI does. The RI doesn't have hard-coded defaults, so supplying your
-     * own "content.types.user.table" means you don't get any of the built-ins, and the built-ins
-     * come from "$JAVA_HOME/lib/content-types.properties".
+     * This isn't what the RI does. The RI doesn't have hard-coded defaults, so supplying your own
+     * "content.types.user.table" means you don't get any of the built-ins, and the built-ins come
+     * from "$JAVA_HOME/lib/content-types.properties".
      */
     private static void applyOverrides() {
         // Get the appropriate InputStream to read overrides from, if any.
@@ -492,8 +494,7 @@ public final class MimeUtils {
         }
     }
 
-    private MimeUtils() {
-    }
+    private MimeUtils() {}
 
     /**
      * Returns true if the given MIME type has an entry in the map.
@@ -535,9 +536,8 @@ public final class MimeUtils {
     }
 
     /**
-     * Returns the registered extension for the given MIME type. Note that some
-     * MIME types map to multiple extensions. This call will return the most
-     * common extension for the given MIME type.
+     * Returns the registered extension for the given MIME type. Note that some MIME types map to
+     * multiple extensions. This call will return the most common extension for the given MIME type.
      *
      * @param mimeType A MIME type (i.e. text/plain)
      * @return The extension for the given MIME type or null iff there is none.
@@ -549,10 +549,11 @@ public final class MimeUtils {
         return mimeTypeToExtensionMap.get(mimeType.split(";")[0]);
     }
 
-    public static String guessMimeTypeFromUriAndMime(final Context context, final Uri uri, final String mime) {
-        Log.d(Config.LOGTAG, "guessMimeTypeFromUriAndMime(" + uri + "," + mime+")");
+    public static String guessMimeTypeFromUriAndMime(
+            final Context context, final Uri uri, final String mime) {
+        Log.d(Config.LOGTAG, "guessMimeTypeFromUriAndMime(" + uri + "," + mime + ")");
         final String mimeFromUri = guessMimeTypeFromUri(context, uri);
-        Log.d(Config.LOGTAG,"mimeFromUri:"+mimeFromUri);
+        Log.d(Config.LOGTAG, "mimeFromUri:" + mimeFromUri);
         if (PATH_PRECEDENCE_MIME_TYPE.contains(mimeFromUri)) {
             return mimeFromUri;
         } else if (mime == null || mime.equals("application/octet-stream")) {
@@ -580,7 +581,8 @@ public final class MimeUtils {
         if (PATH_PRECEDENCE_MIME_TYPE.contains(mimeTypeFromPath)) {
             return mimeTypeFromPath;
         }
-        if (mimeTypeContentResolver != null && !"application/octet-stream".equals(mimeTypeContentResolver)) {
+        if (mimeTypeContentResolver != null
+                && !"application/octet-stream".equals(mimeTypeContentResolver)) {
             return mimeTypeContentResolver;
         }
         if (mimeTypeFromName != null) {
@@ -601,7 +603,8 @@ public final class MimeUtils {
     }
 
     private static String getDisplayName(final Context context, final Uri uri) {
-        try (final Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) {
+        try (final Cursor cursor =
+                context.getContentResolver().query(uri, null, null, null, null)) {
             if (cursor != null && cursor.moveToFirst()) {
                 final int index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
                 if (index == -1) {
@@ -627,7 +630,8 @@ public final class MimeUtils {
         return extractRelevantExtension(path, false);
     }
 
-    public static String extractRelevantExtension(final String path, final boolean ignoreCryptoExtension) {
+    public static String extractRelevantExtension(
+            final String path, final boolean ignoreCryptoExtension) {
         if (Strings.isNullOrEmpty(path)) {
             return null;
         }

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;
         }
     }
@@ -264,7 +266,7 @@ public class XmppUri {
     }
 
     public boolean hasFingerprints() {
-        return fingerprints.size() > 0;
+        return !fingerprints.isEmpty();
     }
 
     public enum FingerprintType {

src/main/java/eu/siacs/conversations/worker/ExportBackupWorker.java 🔗

@@ -14,7 +14,6 @@ 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;
@@ -27,7 +26,6 @@ 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;
@@ -38,7 +36,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;
@@ -59,7 +56,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;
@@ -166,7 +162,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;
@@ -174,7 +171,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 {

src/main/java/eu/siacs/conversations/xml/Element.java 🔗

@@ -1,14 +1,15 @@
 package eu.siacs.conversations.xml;
 
 import androidx.annotation.NonNull;
-
 import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.base.Strings;
 import com.google.common.primitives.Ints;
 import com.google.common.primitives.Longs;
-
+import eu.siacs.conversations.utils.XmlHelper;
+import eu.siacs.conversations.xmpp.Jid;
+import im.conversations.android.xmpp.model.stanza.Message;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Hashtable;
@@ -16,7 +17,6 @@ import java.util.List;
 import java.util.stream.Collectors;
 
 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;
 
@@ -188,9 +188,9 @@ public class Element implements Node {
 		final String jid = this.getAttribute(name);
 		if (jid != null && !jid.isEmpty()) {
 			try {
-				return Jid.ofEscaped(jid);
+				return Jid.of(jid);
 			} catch (final IllegalArgumentException e) {
-				return InvalidJid.of(jid, this instanceof Message);
+				return Jid.ofOrInvalid(jid, this instanceof Message);
 			}
 		}
 		return null;
@@ -205,7 +205,7 @@ public class Element implements Node {
 
 	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;
 	}

src/main/java/eu/siacs/conversations/xmpp/InvalidJid.java 🔗

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

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;
+            }
+        }
+    }
 }

src/main/java/eu/siacs/conversations/xmpp/WrappedJid.java 🔗

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

src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java 🔗

@@ -2357,7 +2357,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);
@@ -2522,7 +2522,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);
@@ -2759,7 +2759,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;
     }
 

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;
@@ -40,10 +38,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 +349,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 +390,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 +413,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 +450,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,11 +770,14 @@ public class JingleConnectionManager extends AbstractConnectionManager {
                 if (hasMatchingRtpSession(account, with, media) != null) {
                     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");
+                throw new IllegalStateException(
+                        "There is already a running RTP session. This should have been caught by"
+                                + " the UI");
             }
             final CallIntegration callIntegration =
                     new CallIntegration(mXmppConnectionService.getApplicationContext());

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java 🔗

@@ -8,7 +8,6 @@ import android.os.Environment;
 
 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;
@@ -25,7 +24,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;
@@ -49,7 +47,6 @@ 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;
 
@@ -72,6 +69,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 {
@@ -288,7 +289,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");
         }
     }
 
@@ -635,7 +637,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());
@@ -704,7 +707,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());
@@ -783,14 +787,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);
                             }
                         });
@@ -1082,7 +1088,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;
     }
@@ -1179,7 +1186,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;
             }
@@ -1264,7 +1272,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;
         }
@@ -1352,7 +1361,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();
@@ -1455,7 +1465,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);
@@ -1498,10 +1509,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);
@@ -1615,7 +1626,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;
         }
@@ -1737,7 +1749,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);
                     }
@@ -1832,7 +1845,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();
@@ -1927,7 +1941,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);
@@ -1966,7 +1981,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;
                     },
@@ -2080,9 +2096,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;
         };
     }
 
@@ -2126,13 +2143,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));
         }
     }
 
@@ -2205,14 +2223,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);
         }
     }
@@ -2222,7 +2244,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) {
@@ -2255,7 +2278,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()) {
@@ -2457,7 +2481,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;
                 }
@@ -2659,7 +2684,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);
@@ -2714,7 +2740,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?
@@ -2746,8 +2773,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);

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

src/main/java/im/conversations/android/xmpp/model/Extension.java 🔗

@@ -3,11 +3,8 @@ package im.conversations.android.xmpp.model;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.Iterables;
-
 import eu.siacs.conversations.xml.Element;
-
 import im.conversations.android.xmpp.ExtensionFactory;
-
 import java.util.Collection;
 
 public class Extension extends Element {
@@ -39,6 +36,14 @@ public class Extension extends Element {
         return clazz.cast(extension);
     }
 
+    public <E extends Extension> E getOnlyExtension(final Class<E> clazz) {
+        final var extensions = getExtensions(clazz);
+        if (extensions.size() == 1) {
+            return Iterables.getOnlyElement(extensions);
+        }
+        return null;
+    }
+
     public <E extends Extension> Collection<E> getExtensions(final Class<E> clazz) {
         return Collections2.transform(
                 Collections2.filter(getChildren(), clazz::isInstance), clazz::cast);

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

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

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;

src/main/res/drawable/ic_description_48dp.xml 🔗

@@ -1,5 +1,12 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="48dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="48dp">
-      
-    <path android:fillColor="@android:color/white" android:pathData="M14,2L6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6zM16,18L8,18v-2h8v2zM16,14L8,14v-2h8v2zM13,9L13,3.5L18.5,9L13,9z"/>
-    
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="48dp"
+    android:height="48dp"
+    android:tint="#FFFFFF"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M14,2L6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6zM16,18L8,18v-2h8v2zM16,14L8,14v-2h8v2zM13,9L13,3.5L18.5,9L13,9z" />
+
 </vector>

src/main/res/drawable/ic_slideshow_48dp.xml 🔗

@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="48dp"
+    android:height="48dp"
+    android:tint="@android:color/white"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M10,8v8l5,-4 -5,-4zM19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19L5,5h14v14z" />
+
+</vector>

src/main/res/drawable/ic_table_48dp.xml 🔗

@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="48dp"
+    android:height="48dp"
+    android:tint="@android:color/white"
+    android:viewportWidth="960"
+    android:viewportHeight="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM440,600L200,600L200,760Q200,760 200,760Q200,760 200,760L440,760L440,600ZM520,600L520,760L760,760Q760,760 760,760Q760,760 760,760L760,600L520,600ZM440,520L440,360L200,360L200,520L440,520ZM520,520L760,520L760,360L520,360L520,520ZM200,280L760,280L760,200Q760,200 760,200Q760,200 760,200L200,200Q200,200 200,200Q200,200 200,200L200,280Z" />
+</vector>

src/main/res/layout/item_message_content.xml 🔗

@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <layout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto">
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
 
     <merge>
 
@@ -104,14 +105,17 @@
             android:layout_height="wrap_content"
             android:layout_marginHorizontal="10dp"
             android:layout_marginVertical="4dp"
-            android:visibility="gone">
+            android:visibility="gone"
+            tools:visibility="visible">
 
-            <ImageButton
+            <com.google.android.material.button.MaterialButton
+                style="?attr/materialIconButtonOutlinedStyle"
                 android:id="@+id/play_pause"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_centerVertical="true"
-                android:background="?android:selectableItemBackgroundBorderless" />
+                app:iconSize="26dp"
+                app:icon="@drawable/ic_play_arrow_24dp" />
 
             <TextView
                 android:id="@+id/runtime"

src/main/res/values-es/strings.xml 🔗

@@ -529,7 +529,7 @@
     <string name="this_field_is_required">Este campo es requerido</string>
     <string name="correct_message">Corregir mensaje</string>
     <string name="send_corrected_message">Enviar mensaje corregido</string>
-    <string name="no_keys_just_confirm">Ya has confiado la huella digital de esta persona. Al seleccionar “Listo” solo estás confirmando que %s es parte de este chat grupal.</string>
+    <string name="no_keys_just_confirm">Ya has confiado en la huella digital de esta persona. Al seleccionar “Hecho” solo estás confirmando que %s es parte de este chat grupal.</string>
     <string name="this_account_is_disabled">Has deshabilitado esta cuenta</string>
     <string name="security_error_invalid_file_access">Error de seguridad: ¡Acceso a archivo inválido!</string>
     <string name="no_application_to_share_uri">No se ha encontrado ninguna aplicación para compartir la URI</string>
@@ -1011,7 +1011,7 @@
     <string name="contact_uses_unverified_keys">Tu contacto utiliza dispositivos no verificados. Escanea su código QR para realizar la verificación e impedir ataques MITM activos.</string>
     <string name="log_out">Desconectarse</string>
     <string name="account_state_logged_out">Desconectado</string>
-    <string name="unverified_devices">Está utilizando dispositivos no verificados. Escanea el código QR en tus otros dispositivos para realizar la verificación e impedir ataques MITM activos.</string>
+    <string name="unverified_devices">Estás utilizando dispositivos no verificados. Escanea el código QR de tus otros dispositivos para realizar la verificación e impedir ataques MITM activos.</string>
     <string name="report_spam_and_block">Informar de spam y bloquear al spammer</string>
     <string name="report_spam">Informar sobre spam</string>
     <string name="welcome_header_quicksy">¡Bienvenido a Quicksy!</string>
@@ -1059,7 +1059,7 @@
     <string name="pref_title_trust_system_ca_store">Organismos de certificación</string>
     <string name="pref_title_trust_system_ca_store_summary">Confiar en los certificados CA del sistema</string>
     <string name="detect_mim">Requerir enlace al canal</string>
-    <string name="detect_mim_summary">La vinculación de canales puede detectar algunos ataques al intermediario</string>
+    <string name="detect_mim_summary">La vinculación de canales puede detectar algunos ataques de intermediario</string>
     <string name="pref_category_server_connection">Conexión al servidor</string>
     <string name="pref_category_operating_system">Sistema operativo</string>
     <string name="pref_category_on_this_device">En el dispositivo</string>
@@ -1103,7 +1103,7 @@
     <string name="call_is_using_speaker">La llamada está usando el altavoz.</string>
     <string name="call_is_using_bluetooth">La llamada está usando bluetooth.</string>
     <string name="video_is_disabled_tap_to_enable">Video desactivado. Toca para activar.</string>
-    <string name="server_info_login_mechanism">Apartado de inicio de sesión</string>
+    <string name="server_info_login_mechanism">Método de acceso</string>
     <string name="could_not_add_reaction">No se pudo agregar la reacción</string>
     <string name="add_reaction">Agregar reacción…</string>
     <string name="add_reaction_title">Agregar reacción</string>

src/main/res/values-fi/strings.xml 🔗

@@ -319,12 +319,12 @@
     <string name="notification_restored_backup_title">Varmuuskopiosi on palautettu</string>
     <string name="notification_restored_backup_subtitle">Älä unohda ottaa tiliä käyttöön.</string>
     <string name="choose_file">Valitse tiedosto</string>
-    <string name="receiving_x_file">Vastaanotetaan %1$s (%2$d%% valmis)</string>
+    <string name="receiving_x_file">Vastaanotetaan %1$s (%2$d% % valmiina)</string>
     <string name="download_x_file">Lataa %s</string>
     <string name="delete_x_file">Poista %s</string>
     <string name="file">tiedosto</string>
     <string name="open_x_file">Avaa %s</string>
-    <string name="sending_file">Lähetetään (%1$d%% valmis)</string>
+    <string name="sending_file">lähetetään (%1$d% % valmiina)</string>
     <string name="preparing_file">Valmistellaan tiedoston lähettämistä</string>
     <string name="x_file_offered_for_download">%s tarjottu ladattavaksi</string>
     <string name="cancel_transmission">Peru siirto</string>

src/main/res/values-ja/strings.xml 🔗

@@ -454,7 +454,7 @@
     <string name="download_failed_invalid_file">ダウンロード失敗: 無効なファイル</string>
     <string name="account_status_tor_unavailable">Tor ネットワークが利用できません</string>
     <string name="account_status_bind_failure">バインド失敗</string>
-    <string name="account_status_host_unknown">そのサーバーはこのドメインに責任を持ちません</string>
+    <string name="account_status_host_unknown">このドメインに責任を持ちません</string>
     <string name="server_info_broken">壊れています</string>
     <string name="pref_presence_settings">在席状況</string>
     <string name="pref_away_when_screen_off">デバイスがロックされているときは離席</string>
@@ -1089,4 +1089,5 @@
     <string name="pref_chat_bubbles">ふきだし</string>
     <string name="pref_chat_bubbles_summary">背景色、文字サイズ、プロフィール画像など</string>
     <string name="pref_title_bubbles">ふきだし</string>
+    <string name="account_status_connection_timeout">接続タイムアウト</string>
 </resources>

src/main/res/values-pt-rBR/strings.xml 🔗

@@ -27,7 +27,7 @@
     <string name="minutes_ago">%d minutos atrás</string>
     <plurals name="x_unread_conversations">
         <item quantity="one">%d conversa não lida</item>
-        <item quantity="many">%d conversas não lidas</item>
+        <item quantity="many">%d de conversas não lidas</item>
         <item quantity="other">%d conversas não lidas</item>
     </plurals>
     <string name="sending">enviando…</string>
@@ -444,7 +444,7 @@
     <string name="dialog_manage_certs_positivebutton">Excluir a seleção</string>
     <plurals name="toast_delete_certificates">
         <item quantity="one">%d certificado cancelado</item>
-        <item quantity="many">%d certificados cancelados</item>
+        <item quantity="many">%d de certificados cancelados</item>
         <item quantity="other">%d certificados cancelados</item>
     </plurals>
     <string name="pref_quick_action_summary">Troca o botão \"Enviar\" pelo de ação rápida</string>
@@ -502,7 +502,7 @@
     <string name="connected_accounts">%1$d de %2$d contas conectadas</string>
     <plurals name="x_messages">
         <item quantity="one">%d mensagem</item>
-        <item quantity="many">%d mensagens</item>
+        <item quantity="many">%d de mensagens</item>
         <item quantity="other">%d mensagens</item>
     </plurals>
     <string name="load_more_messages">Carregar mais mensagens</string>
@@ -628,32 +628,32 @@
     <string name="distrust_omemo_key_text">Tem certeza que deseja remover a verificação para este dispositivo?\nEste dispositivo e as mensagens oriundas dele serão marcadas como \"não confiáveis\".</string>
     <plurals name="seconds">
         <item quantity="one">%d segundo</item>
-        <item quantity="many">%d segundos</item>
+        <item quantity="many">%d de segundos</item>
         <item quantity="other">%d segundos</item>
     </plurals>
     <plurals name="minutes">
         <item quantity="one">%d minuto</item>
-        <item quantity="many">%d minutos</item>
+        <item quantity="many">%d de minutos</item>
         <item quantity="other">%d minutos</item>
     </plurals>
     <plurals name="hours">
         <item quantity="one">%d hora</item>
-        <item quantity="many">%d horas</item>
+        <item quantity="many">%d de horas</item>
         <item quantity="other">%d horas</item>
     </plurals>
     <plurals name="days">
         <item quantity="one">%d dia</item>
-        <item quantity="many">%d dias</item>
+        <item quantity="many">%d de dias</item>
         <item quantity="other">%d dias</item>
     </plurals>
     <plurals name="weeks">
         <item quantity="one">%d semana</item>
-        <item quantity="many">%d semanas</item>
+        <item quantity="many">%d de semanas</item>
         <item quantity="other">%d semanas</item>
     </plurals>
     <plurals name="months">
         <item quantity="one">%d mês</item>
-        <item quantity="many">%d meses</item>
+        <item quantity="many">%d de meses</item>
         <item quantity="other">%d meses</item>
     </plurals>
     <string name="pref_automatically_delete_messages">Exclusão automática de mensagens</string>
@@ -928,18 +928,18 @@
     <string name="outgoing_call">Chamada realizada</string>
     <string name="missed_call">Chamada perdida</string>
     <plurals name="n_missed_calls_from_x">
-        <item quantity="one">%1$d chamada perdida para %2$s</item>
-        <item quantity="many">%1$d chamadas perdidas para %2$s</item>
-        <item quantity="other">%1$d chamadas perdidas para %2$s</item>
+        <item quantity="one">%1$d chamada perdida de %2$s</item>
+        <item quantity="many">%1$d de chamadas perdidas de %2$s</item>
+        <item quantity="other">%1$d chamadas perdidas de %2$s</item>
     </plurals>
     <plurals name="n_missed_calls">
         <item quantity="one">%d chamada perdida</item>
-        <item quantity="many">%d chamadas perdidas</item>
+        <item quantity="many">%d de chamadas perdidas</item>
         <item quantity="other">%d chamadas perdidas</item>
     </plurals>
     <plurals name="n_missed_calls_from_m_contacts">
         <item quantity="one">%1$d chamadas perdidas de %2$d contato</item>
-        <item quantity="many">%1$d chamadas perdidas de %2$d contatos</item>
+        <item quantity="many">%1$d de chamadas perdidas de %2$d contatos</item>
         <item quantity="other">%1$d chamadas perdidas de %2$d contatos</item>
     </plurals>
     <string name="audio_call">Chamada de áudio</string>
@@ -967,7 +967,7 @@
     <string name="add_contact_or_create_or_join_group_chat">Adicionar contato, criar ou associar-se a uma conversa em grupo ou descobrir canais</string>
     <plurals name="view_users">
         <item quantity="one">Ver %1$d participante</item>
-        <item quantity="many">Ver %1$d participantes</item>
+        <item quantity="many">Ver %1$d de participantes</item>
         <item quantity="other">Ver %1$d participantes</item>
     </plurals>
     <plurals name="some_messages_could_not_be_delivered">

src/playstore/java/eu/siacs/conversations/services/PushManagementService.java 🔗

@@ -1,11 +1,9 @@
 package eu.siacs.conversations.services;
 
 import android.util.Log;
-
 import com.google.android.gms.common.ConnectionResult;
 import com.google.android.gms.common.GoogleApiAvailabilityLight;
 import com.google.firebase.messaging.FirebaseMessaging;
-
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.entities.Account;
@@ -15,7 +13,6 @@ import eu.siacs.conversations.xml.Namespace;
 import eu.siacs.conversations.xmpp.Jid;
 import eu.siacs.conversations.xmpp.XmppConnection;
 import eu.siacs.conversations.xmpp.forms.Data;
-
 import im.conversations.android.xmpp.model.stanza.Iq;
 
 public class PushManagementService {
@@ -53,7 +50,7 @@ public class PushManagementService {
                                 if (response.getType() == Iq.Type.RESULT && data != null) {
                                     final Jid jid;
                                     try {
-                                        jid = Jid.ofEscaped(data.getValue("jid"));
+                                        jid = Jid.of(data.getValue("jid"));
                                     } catch (final IllegalArgumentException e) {
                                         Log.d(
                                                 Config.LOGTAG,
@@ -70,7 +67,8 @@ public class PushManagementService {
                                     Log.d(
                                             Config.LOGTAG,
                                             account.getJid().asBareJid()
-                                                    + ": failed to enable push. invalid response from app server "
+                                                    + ": failed to enable push. invalid response"
+                                                    + " from app server "
                                                     + response);
                                 }
                             });
@@ -123,7 +121,8 @@ public class PushManagementService {
                             } catch (Exception e) {
                                 Log.d(
                                         Config.LOGTAG,
-                                        "unable to get Firebase instance token due to bug in library ",
+                                        "unable to get Firebase instance token due to bug in"
+                                                + " library ",
                                         e);
                                 return;
                             }

src/quicksy/java/eu/siacs/conversations/entities/Entry.java 🔗

@@ -1,21 +1,16 @@
 package eu.siacs.conversations.entities;
 
 import android.util.Base64;
-
 import com.google.common.base.Charsets;
 import com.google.common.hash.Hashing;
-
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
+import eu.siacs.conversations.android.PhoneNumberContact;
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xmpp.Jid;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
-import eu.siacs.conversations.android.PhoneNumberContact;
-import eu.siacs.conversations.xml.Element;
-import eu.siacs.conversations.xmpp.Jid;
-
 public class Entry implements Comparable<Entry> {
     private final List<Jid> jids;
     private final String number;
@@ -47,23 +42,25 @@ public class Entry implements Comparable<Entry> {
         return entries;
     }
 
-    public static String statusQuo(final Collection<PhoneNumberContact> phoneNumberContacts, Collection<Contact> systemContacts) {
+    public static String statusQuo(
+            final Collection<PhoneNumberContact> phoneNumberContacts,
+            Collection<Contact> systemContacts) {
         return statusQuo(ofPhoneNumberContactsAndContacts(phoneNumberContacts, systemContacts));
     }
 
     private static String statusQuo(final List<Entry> entries) {
         Collections.sort(entries);
         StringBuilder builder = new StringBuilder();
-        for(Entry entry : entries) {
+        for (Entry entry : entries) {
             if (builder.length() != 0) {
                 builder.append('\u001d');
             }
             builder.append(entry.getNumber());
             List<Jid> jids = entry.getJids();
             Collections.sort(jids);
-            for(Jid jid : jids) {
+            for (Jid jid : jids) {
                 builder.append('\u001e');
-                builder.append(jid.asBareJid().toEscapedString());
+                builder.append(jid.asBareJid().toString());
             }
         }
         @SuppressWarnings("deprecation")
@@ -71,12 +68,16 @@ public class Entry implements Comparable<Entry> {
         return new String(Base64.encode(sha1, Base64.DEFAULT)).trim();
     }
 
-    private static List<Entry> ofPhoneNumberContactsAndContacts(final Collection<PhoneNumberContact> phoneNumberContacts, Collection<Contact> systemContacts) {
+    private static List<Entry> ofPhoneNumberContactsAndContacts(
+            final Collection<PhoneNumberContact> phoneNumberContacts,
+            Collection<Contact> systemContacts) {
         final ArrayList<Entry> entries = new ArrayList<>();
-        for(Contact contact : systemContacts) {
-            final PhoneNumberContact phoneNumberContact = PhoneNumberContact.findByUri(phoneNumberContacts, contact.getSystemAccount());
+        for (Contact contact : systemContacts) {
+            final PhoneNumberContact phoneNumberContact =
+                    PhoneNumberContact.findByUri(phoneNumberContacts, contact.getSystemAccount());
             if (phoneNumberContact != null && phoneNumberContact.getPhoneNumber() != null) {
-                Entry entry = findOrCreateByPhoneNumber(entries, phoneNumberContact.getPhoneNumber());
+                Entry entry =
+                        findOrCreateByPhoneNumber(entries, phoneNumberContact.getPhoneNumber());
                 entry.jids.add(contact.getJid().asBareJid());
             }
         }
@@ -84,7 +85,7 @@ public class Entry implements Comparable<Entry> {
     }
 
     private static Entry findOrCreateByPhoneNumber(final List<Entry> entries, String number) {
-        for(Entry entry : entries) {
+        for (Entry entry : entries) {
             if (entry.number.equals(number)) {
                 return entry;
             }

src/quicksy/java/eu/siacs/conversations/ui/EnterNameActivity.java 🔗

@@ -3,16 +3,13 @@ package eu.siacs.conversations.ui;
 import android.content.Intent;
 import android.os.Bundle;
 import android.view.View;
-
 import androidx.databinding.DataBindingUtil;
-
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.databinding.ActivityEnterNameBinding;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.services.AbstractQuickConversationsService;
 import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.utils.AccountUtils;
-
 import java.util.concurrent.atomic.AtomicBoolean;
 
 public class EnterNameActivity extends XmppActivity
@@ -46,18 +43,18 @@ public class EnterNameActivity extends XmppActivity
         if (AbstractQuickConversationsService.isQuicksyPlayStore()) {
             intent = new Intent(getApplicationContext(), StartConversationActivity.class);
             intent.putExtra("init", true);
-            intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString());
+            intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toString());
         } else {
             intent = new Intent(this, PublishProfilePictureActivity.class);
             intent.putExtra("setup", true);
         }
-        intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString());
+        intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toString());
         startActivity(intent);
         finish();
     }
 
     @Override
-    public void onSaveInstanceState(Bundle savedInstanceState) {
+    public void onSaveInstanceState(final Bundle savedInstanceState) {
         savedInstanceState.putBoolean("set_nick", this.setNick.get());
         super.onSaveInstanceState(savedInstanceState);
     }

src/quicksy/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java 🔗

@@ -1,22 +1,18 @@
 package eu.siacs.conversations.utils;
 
 import android.content.Context;
-import android.telephony.TelephonyManager;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-
 import eu.siacs.conversations.xmpp.Jid;
 import io.michaelrocks.libphonenumber.android.NumberParseException;
 import io.michaelrocks.libphonenumber.android.PhoneNumberUtil;
 import io.michaelrocks.libphonenumber.android.Phonenumber;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
 
 public class PhoneNumberUtilWrapper {
 
     private static volatile PhoneNumberUtil instance;
 
-
     public static String getCountryForCode(String code) {
         Locale locale = new Locale("", code);
         return locale.getDisplayCountry();
@@ -24,20 +20,28 @@ public class PhoneNumberUtilWrapper {
 
     public static String toFormattedPhoneNumber(Context context, Jid jid) {
         try {
-            return getInstance(context).format(toPhoneNumber(context, jid), PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL).replace(' ','\u202F');
+            return getInstance(context)
+                    .format(
+                            toPhoneNumber(context, jid),
+                            PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL)
+                    .replace(' ', '\u202F');
         } catch (Exception e) {
-            return jid.getEscapedLocal();
+            return jid.getLocal();
         }
     }
 
-    public static Phonenumber.PhoneNumber toPhoneNumber(Context context, Jid jid) throws NumberParseException {
-        return getInstance(context).parse(jid.getEscapedLocal(), "de");
+    public static Phonenumber.PhoneNumber toPhoneNumber(Context context, Jid jid)
+            throws NumberParseException {
+        return getInstance(context).parse(jid.getLocal(), "de");
     }
 
-    public static String normalize(Context context, String input) throws IllegalArgumentException, NumberParseException {
-        final Phonenumber.PhoneNumber number = getInstance(context).parse(input, LocationProvider.getUserCountry(context));
+    public static String normalize(Context context, String input)
+            throws IllegalArgumentException, NumberParseException {
+        final Phonenumber.PhoneNumber number =
+                getInstance(context).parse(input, LocationProvider.getUserCountry(context));
         if (!getInstance(context).isValidNumber(number)) {
-            throw new IllegalArgumentException(String.format("%s is not a valid phone number", input));
+            throw new IllegalArgumentException(
+                    String.format("%s is not a valid phone number", input));
         }
         return normalize(context, number);
     }
@@ -54,7 +58,6 @@ public class PhoneNumberUtilWrapper {
                 if (localInstance == null) {
                     instance = localInstance = PhoneNumberUtil.createInstance(context);
                 }
-
             }
         }
         return localInstance;
@@ -63,10 +66,10 @@ public class PhoneNumberUtilWrapper {
     public static List<Country> getCountries(final Context context) {
         List<Country> countries = new ArrayList<>();
         for (String region : getInstance(context).getSupportedRegions()) {
-            countries.add(new Country(region, getInstance(context).getCountryCodeForRegion(region)));
+            countries.add(
+                    new Country(region, getInstance(context).getCountryCodeForRegion(region)));
         }
         return countries;
-
     }
 
     public static class Country implements Comparable<Country> {
@@ -97,5 +100,4 @@ public class PhoneNumberUtilWrapper {
             return name.compareTo(o.name);
         }
     }
-
 }