diff --git a/CHANGELOG.md b/CHANGELOG.md index 9516c25bd2476b23b1f137e4a72d75e20ac5ea61..5a991842efe4354883a8e783147f3926f687c810 100644 --- a/CHANGELOG.md +++ b/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 diff --git a/fastlane/metadata/android/de-DE/changelogs/4213104.txt b/fastlane/metadata/android/de-DE/changelogs/4213104.txt new file mode 100644 index 0000000000000000000000000000000000000000..fabfac695af6a4173e7af34c49715beac984534b --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/4213104.txt @@ -0,0 +1,2 @@ +* Verwendung von SASL SCRAM Downgrade Protection (XEP-0474) +* Reaktionen an MUC PMs senden, damit die XMPP-Adresse korrekt ist diff --git a/fastlane/metadata/android/de-DE/changelogs/4213204.txt b/fastlane/metadata/android/de-DE/changelogs/4213204.txt new file mode 100644 index 0000000000000000000000000000000000000000..c5ea7dbb555bf730494cb25a88b369f3493b43e5 --- /dev/null +++ b/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 diff --git a/fastlane/metadata/android/en-US/changelogs/4213104.txt b/fastlane/metadata/android/en-US/changelogs/4213104.txt new file mode 100644 index 0000000000000000000000000000000000000000..886e2fc13126f2cea2102fdb48b3cfe9ecb2f9ae --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/4213104.txt @@ -0,0 +1,2 @@ +* Make use of SASL SCRAM Downgrade Protection (XEP-0474) +* Send reactions to MUC PMs to correct JID diff --git a/fastlane/metadata/android/en-US/changelogs/4213204.txt b/fastlane/metadata/android/en-US/changelogs/4213204.txt new file mode 100644 index 0000000000000000000000000000000000000000..ae676ad87b4111770e798951049eeadea3864ac4 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/4213204.txt @@ -0,0 +1,4 @@ +* 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 diff --git a/fastlane/metadata/android/es-ES/changelogs/4213104.txt b/fastlane/metadata/android/es-ES/changelogs/4213104.txt new file mode 100644 index 0000000000000000000000000000000000000000..d029b2c27c85805e9a3f3e021a3e4de06aaad0a7 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/4213104.txt @@ -0,0 +1,2 @@ +* Hicimos uso de la protección contra degradación de SCRAM SASL (XEP-0474) +* Envíe reacciones a los mensajes privados de MUC para corregir el JID diff --git a/fastlane/metadata/android/et/changelogs/42043.txt b/fastlane/metadata/android/et/changelogs/42043.txt new file mode 100644 index 0000000000000000000000000000000000000000..5ce423f963fb158a8098804345ffa19489d64038 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/42043.txt @@ -0,0 +1 @@ +* Parandasime regressiooni, mis tekkis failide teisaldamisel P2P-ühenduse kaudu diff --git a/fastlane/metadata/android/et/changelogs/42044.txt b/fastlane/metadata/android/et/changelogs/42044.txt new file mode 100644 index 0000000000000000000000000000000000000000..916dd4e19767dddd24d1f161ee8e775467ac50a2 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/42044.txt @@ -0,0 +1,3 @@ +* Parandasime sõnumite kordussaatmise SASL2 kasutamisel +* Parandasime musta videovaate tekkimise mõnede seadmete puhul +* Parandasime rakenduse kokkujooksmise tühja salasõna puhul diff --git a/fastlane/metadata/android/et/changelogs/42046.txt b/fastlane/metadata/android/et/changelogs/42046.txt new file mode 100644 index 0000000000000000000000000000000000000000..a9aa11eadd8566dd1df58a12dee1911d5dacd5b1 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/42046.txt @@ -0,0 +1 @@ +* Lõimisime UnifiedPushi tõuketeenuste levitajana teistele UnifiedPushi kasutajatele nagu Tusky ja Fedilab diff --git a/fastlane/metadata/android/et/changelogs/42047.txt b/fastlane/metadata/android/et/changelogs/42047.txt new file mode 100644 index 0000000000000000000000000000000000000000..d65bd1e5f7a847b6e161263aa1414eaaa41c81d3 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/42047.txt @@ -0,0 +1 @@ +* Parandasime kokkujooksmise, mis ilmnes UnifiedPushi kasutamisel tõuketeenuste levitajana diff --git a/fastlane/metadata/android/et/changelogs/42050.txt b/fastlane/metadata/android/et/changelogs/42050.txt new file mode 100644 index 0000000000000000000000000000000000000000..cc51ce1f3292e2e2403030f92a2f98d9f9e34a9f --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/42050.txt @@ -0,0 +1 @@ +* Suurendasime profiilipiltide nurkade raadiust diff --git a/fastlane/metadata/android/et/changelogs/42059.txt b/fastlane/metadata/android/et/changelogs/42059.txt new file mode 100644 index 0000000000000000000000000000000000000000..1d47a1cf89d9109cb39cf389d7e915ebc9880918 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/42059.txt @@ -0,0 +1,2 @@ +* Kompileerime jälle arvestades Androidi SDK versiooni 33 +* Parandasime vead serveritega, mis ilmnesid SASL2 kasutamisel ilma lõimitud meediavoo halduseta (inline Stream Management) diff --git a/fastlane/metadata/android/et/changelogs/42060.txt b/fastlane/metadata/android/et/changelogs/42060.txt new file mode 100644 index 0000000000000000000000000000000000000000..c19cd31ece12079fc84435ca2b500b5e96e42e67 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/42060.txt @@ -0,0 +1 @@ +* Parandasime olukorra, kus „q“ tuvastus kirillitsana diff --git a/fastlane/metadata/android/et/changelogs/4213104.txt b/fastlane/metadata/android/et/changelogs/4213104.txt new file mode 100644 index 0000000000000000000000000000000000000000..3631b51f5702af963c7b212620ffd6a5d3b45048 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/4213104.txt @@ -0,0 +1,2 @@ +* Võtsime kasutusele SASL SCRAM madaldusründe kaitse (SASL SCRAM Downgrade Protection XEP-0474) +* saadame jututoa sõnumite reakstioonid õigetele kasutajatele diff --git a/fastlane/metadata/android/et/changelogs/4213204.txt b/fastlane/metadata/android/et/changelogs/4213204.txt new file mode 100644 index 0000000000000000000000000000000000000000..a879dc812c17864b030587e1dada751d95f2d351 --- /dev/null +++ b/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 diff --git a/fastlane/metadata/android/gl-ES/changelogs/4213104.txt b/fastlane/metadata/android/gl-ES/changelogs/4213104.txt new file mode 100644 index 0000000000000000000000000000000000000000..3eae5234324d4a1e80ad4db963f7e65e36661129 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/4213104.txt @@ -0,0 +1,2 @@ +* Uso de SASL SCRAM Downgrade Protection (XEP-0474) +* Envío de reaccións en MUC PMs ao JID correcto diff --git a/fastlane/metadata/android/pl-PL/changelogs/4213104.txt b/fastlane/metadata/android/pl-PL/changelogs/4213104.txt new file mode 100644 index 0000000000000000000000000000000000000000..7946673f6abee73e4090c1b760b2fb7151e2ad8d --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/4213104.txt @@ -0,0 +1,2 @@ +* Implementacja ochrony przed obniżeniem poziomu bezpieczeństwa SASL SCRAM (XEP-0474) +* Wysyłanie reakcji w prywatnych wiadomościach w rozmowie grupowej do właściwego JID diff --git a/fastlane/metadata/android/pl-PL/changelogs/4213204.txt b/fastlane/metadata/android/pl-PL/changelogs/4213204.txt new file mode 100644 index 0000000000000000000000000000000000000000..ff73979e3e854126aca483077d95048e99e32196 --- /dev/null +++ b/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 diff --git a/fastlane/metadata/android/ru-RU/changelogs/4213104.txt b/fastlane/metadata/android/ru-RU/changelogs/4213104.txt new file mode 100644 index 0000000000000000000000000000000000000000..73bc95031fde7804462c2893fc8c9f69a1ea4c5e --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/4213104.txt @@ -0,0 +1,2 @@ +* Используется защита от понижения SASL SCRAM (XEP-0474) +* Реакции отправляются в MUC PM для корректировки JID diff --git a/fastlane/metadata/android/ru-RU/changelogs/4213204.txt b/fastlane/metadata/android/ru-RU/changelogs/4213204.txt new file mode 100644 index 0000000000000000000000000000000000000000..14f4c0400134be6289f4623cf0b17a9dbe8513f2 --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/4213204.txt @@ -0,0 +1,4 @@ +* Добавлена возможность приостановить запись звука нажатием на таймер +* Исправлены реакции в MUC PM +* Прекращён приём "резервных сообщений" для реакций, уведомлений о доставке и маркеров отображения +* Добавлено ещё несколько значков для просмотра медиа diff --git a/fastlane/metadata/android/sq/changelogs/4213204.txt b/fastlane/metadata/android/sq/changelogs/4213204.txt new file mode 100644 index 0000000000000000000000000000000000000000..c44d9a8f9422a0e2d566590929af0cc65a8e2c2e --- /dev/null +++ b/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 diff --git a/fastlane/metadata/android/uk/changelogs/4213104.txt b/fastlane/metadata/android/uk/changelogs/4213104.txt new file mode 100644 index 0000000000000000000000000000000000000000..151ac4a042c33796d760e0bf9ffbda92d50b7046 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/4213104.txt @@ -0,0 +1,2 @@ +* Використання SASL SCRAM Downgrade Protection (XEP-0474) +* Надсилання реакцій у приватні повідомлення MUC на правильний JID diff --git a/fastlane/metadata/android/zh-CN/changelogs/4213104.txt b/fastlane/metadata/android/zh-CN/changelogs/4213104.txt new file mode 100644 index 0000000000000000000000000000000000000000..69a28dd68019fff1b5084304a3fbfaa6381d540c --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/4213104.txt @@ -0,0 +1,2 @@ +* 使用 SASL SCRAM 降级保护(XEP-0474) +* 将 MUC 私信中的回应发送至正确的 JID diff --git a/fastlane/metadata/android/zh-CN/changelogs/4213204.txt b/fastlane/metadata/android/zh-CN/changelogs/4213204.txt new file mode 100644 index 0000000000000000000000000000000000000000..d81e465d52d704a11390e9a38e3ba61846d89df9 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/4213204.txt @@ -0,0 +1,4 @@ +* 允许通过点按计时器暂停录制音频 +* 修复 MUC 私信中的回应 +* 停止接收回应,回执和显示标记的“回退消息” +* 添加更多媒体预览图标 diff --git a/src/cheogram/java/com/cheogram/ExportBackupService.java b/src/cheogram/java/com/cheogram/ExportBackupService.java index 9a121f29bca5978f23926d9d31d1c1e2aeca6d1b..f0157868d0782f21526934a50fb39e496d1bf466 100644 --- a/src/cheogram/java/com/cheogram/ExportBackupService.java +++ b/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] )); diff --git a/src/cheogram/java/com/cheogram/android/FinishOnboarding.java b/src/cheogram/java/com/cheogram/android/FinishOnboarding.java index 86debf794f39d049568644534b92d9d5cd2e934b..fabcd644b0cf89becb8c0e9ed211576ed500729f 100644 --- a/src/cheogram/java/com/cheogram/android/FinishOnboarding.java +++ b/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()); diff --git a/src/cheogram/java/com/cheogram/android/WebxdcPage.java b/src/cheogram/java/com/cheogram/android/WebxdcPage.java index 9499619c0857efe2e69c822afaf8a4cfab217415..f1df4728b2e758ffa3b004dc1fa9fe3ad54f52a4 100644 --- a/src/cheogram/java/com/cheogram/android/WebxdcPage.java +++ b/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 diff --git a/src/cheogram/java/com/cheogram/android/WebxdcUpdate.java b/src/cheogram/java/com/cheogram/android/WebxdcUpdate.java index 570b3a180cfa5b2c8aeea17efdfafdc6add671a9..da37fc0a1cb754eb212fc463dd4cc6dd23c8d8d3 100644 --- a/src/cheogram/java/com/cheogram/android/WebxdcUpdate.java +++ b/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()); diff --git a/src/cheogram/java/eu/siacs/conversations/entities/AccountConfiguration.java b/src/cheogram/java/eu/siacs/conversations/entities/AccountConfiguration.java index 65b6804f9b6e7bb4abac560824bf5958a38dbd0b..8fdf6aa817b2e049b2078b3a7b48aee5f509cb8a 100644 --- a/src/cheogram/java/eu/siacs/conversations/entities/AccountConfiguration.java +++ b/src/cheogram/java/eu/siacs/conversations/entities/AccountConfiguration.java @@ -17,7 +17,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) { diff --git a/src/cheogram/java/eu/siacs/conversations/services/ImportBackupService.java b/src/cheogram/java/eu/siacs/conversations/services/ImportBackupService.java index 1718e4c4652d610207efc6acc32b1f1463fbe5aa..3ba1c15a0c9bad89fed9d3ae2a7f482b90f3a20b 100644 --- a/src/cheogram/java/eu/siacs/conversations/services/ImportBackupService.java +++ b/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())); diff --git a/src/cheogram/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java b/src/cheogram/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java index 0d0f126c7cc7efebe52af23ec75d1af06885880a..1871db6d1a47d7e96d521de8fd94fa121f909efe 100644 --- a/src/cheogram/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java +++ b/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); } diff --git a/src/cheogram/java/eu/siacs/conversations/ui/MagicCreateActivity.java b/src/cheogram/java/eu/siacs/conversations/ui/MagicCreateActivity.java index 53b362b5c22c045a4b013a566fe355ba3f1ff22c..8d47a2dd95ffede4e49685ab225e1c365bc7b6f0 100644 --- a/src/cheogram/java/eu/siacs/conversations/ui/MagicCreateActivity.java +++ b/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); } diff --git a/src/cheogram/java/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/cheogram/java/eu/siacs/conversations/ui/ManageAccountActivity.java index 7176d22546ddb2dfe5bfc1bd356a34f8fb42bbaa..4783d3720b0918bcaed15c3ee45d5903ec1ec4e6 100644 --- a/src/cheogram/java/eu/siacs/conversations/ui/ManageAccountActivity.java +++ b/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); } diff --git a/src/cheogram/java/eu/siacs/conversations/ui/WelcomeActivity.java b/src/cheogram/java/eu/siacs/conversations/ui/WelcomeActivity.java index 8e9199b2cb4fb3b63c2e5c791a13c1b1e98c406f..fad6a203952c886f3cb05b616a90006b0e69d986 100644 --- a/src/cheogram/java/eu/siacs/conversations/ui/WelcomeActivity.java +++ b/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); diff --git a/src/cheogram/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java b/src/cheogram/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java index f0a3fa87d5a8d59395b4bf22a80e7e86ff2ae293..2948a613ed140b4ae2434f029451167a5b435660 100644 --- a/src/cheogram/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java +++ b/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 { diff --git a/src/cheogram/java/eu/siacs/conversations/utils/ProvisioningUtils.java b/src/cheogram/java/eu/siacs/conversations/utils/ProvisioningUtils.java index 593291d95374f4be2f1722b8c46a03fe34ed28dc..7c4d2a29e078071211a923702dc8cd71c1b6bd6d 100644 --- a/src/cheogram/java/eu/siacs/conversations/utils/ProvisioningUtils.java +++ b/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); } diff --git a/src/cheogram/java/eu/siacs/conversations/utils/SignupUtils.java b/src/cheogram/java/eu/siacs/conversations/utils/SignupUtils.java index fbb294e9b1fde88384c465f11edce933d7f33429..bfe265466d08387d68b3e84cd35fce1ad8c4e303 100644 --- a/src/cheogram/java/eu/siacs/conversations/utils/SignupUtils.java +++ b/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; diff --git a/src/conversations/java/eu/siacs/conversations/entities/AccountConfiguration.java b/src/conversations/java/eu/siacs/conversations/entities/AccountConfiguration.java index 65b6804f9b6e7bb4abac560824bf5958a38dbd0b..c3326d8ee81761121fcba634d788242b6811f373 100644 --- a/src/conversations/java/eu/siacs/conversations/entities/AccountConfiguration.java +++ b/src/conversations/java/eu/siacs/conversations/entities/AccountConfiguration.java @@ -5,7 +5,6 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonSyntaxException; import com.google.gson.annotations.SerializedName; - import eu.siacs.conversations.xmpp.Jid; public class AccountConfiguration { @@ -17,7 +16,7 @@ public class AccountConfiguration { public String password; public Jid getJid() { - return Jid.ofEscaped(address); + return Jid.of(address); } public static AccountConfiguration parse(final String input) { @@ -27,24 +26,17 @@ public class AccountConfiguration { } catch (JsonSyntaxException e) { throw new IllegalArgumentException("Not a valid JSON string", e); } - Preconditions.checkArgument( - c.protocol == Protocol.XMPP, - "Protocol must be XMPP" - ); + Preconditions.checkArgument(c.protocol == Protocol.XMPP, "Protocol must be XMPP"); Preconditions.checkArgument( c.address != null && c.getJid().isBareJid() && !c.getJid().isDomainJid(), - "Invalid XMPP address" - ); + "Invalid XMPP address"); Preconditions.checkArgument( - c.password != null && c.password.length() > 0, - "No password specified" - ); + c.password != null && !c.password.isEmpty(), "No password specified"); return c; } public enum Protocol { - @SerializedName("xmpp") XMPP, + @SerializedName("xmpp") + XMPP, } - } - diff --git a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java index dd96468ee87427a2fb4a1ef319650c5ab9ff1abb..c15208bfa1b8c0fc383dadbcce3d92ff95292aed 100644 --- a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java +++ b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java @@ -16,10 +16,8 @@ import android.os.Binder; import android.os.IBinder; import android.provider.OpenableColumns; import android.util.Log; - import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; - import com.google.common.base.Charsets; import com.google.common.base.Stopwatch; import com.google.common.io.CountingInputStream; @@ -27,7 +25,6 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore; @@ -41,14 +38,6 @@ import eu.siacs.conversations.utils.BackupFileHeader; import eu.siacs.conversations.utils.SerialSingleThreadExecutor; import eu.siacs.conversations.worker.ExportBackupWorker; import eu.siacs.conversations.xmpp.Jid; - -import org.bouncycastle.crypto.engines.AESEngine; -import org.bouncycastle.crypto.io.CipherInputStream; -import org.bouncycastle.crypto.modes.AEADBlockCipher; -import org.bouncycastle.crypto.modes.GCMBlockCipher; -import org.bouncycastle.crypto.params.AEADParameters; -import org.bouncycastle.crypto.params.KeyParameter; - import java.io.BufferedReader; import java.io.DataInputStream; import java.io.File; @@ -72,8 +61,13 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; import java.util.zip.ZipException; - import javax.crypto.BadPaddingException; +import org.bouncycastle.crypto.engines.AESEngine; +import org.bouncycastle.crypto.io.CipherInputStream; +import org.bouncycastle.crypto.modes.AEADBlockCipher; +import org.bouncycastle.crypto.modes.GCMBlockCipher; +import org.bouncycastle.crypto.params.AEADParameters; +import org.bouncycastle.crypto.params.KeyParameter; public class ImportBackupService extends Service { @@ -314,10 +308,11 @@ public class ImportBackupService extends Service { final Jid jid = backupFileHeader.getJid(); final Cursor countCursor = db.rawQuery( - "select count(messages.uuid) from messages join conversations on conversations.uuid=messages.conversationUuid join accounts on conversations.accountUuid=accounts.uuid where accounts.username=? and accounts.server=?", - new String[] { - jid.getEscapedLocal(), jid.getDomain().toEscapedString() - }); + "select count(messages.uuid) from messages join conversations on" + + " conversations.uuid=messages.conversationUuid join accounts on" + + " conversations.accountUuid=accounts.uuid where" + + " accounts.username=? and accounts.server=?", + new String[] {jid.getLocal(), jid.getDomain().toString()}); countCursor.moveToFirst(); final int count = countCursor.getInt(0); Log.d( diff --git a/src/conversations/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java b/src/conversations/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java index 9228a5170a8c053f005c9b9a33517fd043dab5e2..874557198c196712980ed227863e736da9f8cb4f 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java @@ -11,13 +11,10 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.databinding.DataBindingUtil; - import com.google.android.material.color.MaterialColors; import com.google.common.base.Strings; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityEasyInviteBinding; @@ -153,7 +150,7 @@ public class EasyOnboardingInviteActivity extends XmppActivity } final Intent launchIntent = getIntent(); final String accountExtra = launchIntent.getStringExtra(EXTRA_ACCOUNT); - final Jid jid = accountExtra == null ? null : Jid.ofEscaped(accountExtra); + final Jid jid = accountExtra == null ? null : Jid.of(accountExtra); if (jid == null) { return; } @@ -163,7 +160,7 @@ public class EasyOnboardingInviteActivity extends XmppActivity public static void launch(final Account account, final Activity context) { final Intent intent = new Intent(context, EasyOnboardingInviteActivity.class); - intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toString()); context.startActivity(intent); } diff --git a/src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java b/src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java index b6d4d452ee6fa28bfa281f12058d5e87d490bf78..bebf9bab67ba4de49b5bfc39cabb8962bff3e451 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java @@ -7,9 +7,7 @@ import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.widget.Toast; - import androidx.databinding.DataBindingUtil; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityMagicCreateBinding; @@ -17,7 +15,6 @@ import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.InstallReferrerUtils; import eu.siacs.conversations.xmpp.Jid; - import java.security.SecureRandom; public class MagicCreateActivity extends XmppActivity implements TextWatcher { @@ -68,15 +65,15 @@ public class MagicCreateActivity extends XmppActivity implements TextWatcher { final boolean fixedUsername; if (this.domain != null && this.username != null) { fixedUsername = true; - jid = Jid.ofLocalAndDomainEscaped(this.username, this.domain); + jid = Jid.ofLocalAndDomain(this.username, this.domain); } else if (this.domain != null) { fixedUsername = false; - jid = Jid.ofLocalAndDomainEscaped(username, this.domain); + jid = Jid.ofLocalAndDomain(username, this.domain); } else { fixedUsername = false; - jid = Jid.ofLocalAndDomainEscaped(username, Config.MAGIC_CREATE_DOMAIN); + jid = Jid.ofLocalAndDomain(username, Config.MAGIC_CREATE_DOMAIN); } - if (!jid.getEscapedLocal().equals(jid.getLocal()) + if (!jid.getLocal().equals(jid.getLocal()) || (this.username == null && username.length() < 3)) { binding.usernameLayout.setError(getString(R.string.invalid_username)); binding.username.requestFocus(); @@ -146,12 +143,11 @@ public class MagicCreateActivity extends XmppActivity implements TextWatcher { binding.fullJid.setVisibility(View.VISIBLE); final Jid jid; if (this.domain == null) { - jid = Jid.ofLocalAndDomainEscaped(username, Config.MAGIC_CREATE_DOMAIN); + jid = Jid.ofLocalAndDomain(username, Config.MAGIC_CREATE_DOMAIN); } else { - jid = Jid.ofLocalAndDomainEscaped(username, this.domain); + jid = Jid.ofLocalAndDomain(username, this.domain); } - binding.fullJid.setText( - getString(R.string.your_full_jid_will_be, jid.toEscapedString())); + binding.fullJid.setText(getString(R.string.your_full_jid_will_be, jid.toString())); binding.usernameLayout.setError(null); } catch (final IllegalArgumentException e) { binding.fullJid.setVisibility(View.INVISIBLE); diff --git a/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java index 6ab32682fd866ac7d906d058220fdbe2ea7598a4..c2a82f8d5cddfa39f08ce263b2afa4e5cc34495a 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java @@ -17,13 +17,10 @@ import android.view.MenuItem; import android.view.View; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.databinding.DataBindingUtil; - import com.google.common.base.Strings; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityManageAccountsBinding; @@ -34,12 +31,10 @@ import eu.siacs.conversations.ui.adapter.AccountAdapter; import eu.siacs.conversations.ui.util.MenuDoubleTabUtil; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.XmppConnection; - -import org.openintents.openpgp.util.OpenPgpApi; - import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import org.openintents.openpgp.util.OpenPgpApi; public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, @@ -95,7 +90,7 @@ public class ManageAccountActivity extends XmppActivity String jid = savedInstanceState.getString(STATE_SELECTED_ACCOUNT); if (jid != null) { try { - this.selectedAccountJid = Jid.ofEscaped(jid); + this.selectedAccountJid = Jid.of(jid); } catch (IllegalArgumentException e) { this.selectedAccountJid = null; } @@ -113,7 +108,7 @@ public class ManageAccountActivity extends XmppActivity public void onSaveInstanceState(@NonNull final Bundle savedInstanceState) { if (selectedAccount != null) { savedInstanceState.putString( - STATE_SELECTED_ACCOUNT, selectedAccount.getJid().asBareJid().toEscapedString()); + STATE_SELECTED_ACCOUNT, selectedAccount.getJid().asBareJid().toString()); } super.onSaveInstanceState(savedInstanceState); } @@ -132,7 +127,7 @@ public class ManageAccountActivity extends XmppActivity menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(false); menu.findItem(R.id.mgmt_account_publish_avatar).setVisible(false); } - menu.setHeaderTitle(this.selectedAccount.getJid().asBareJid().toEscapedString()); + menu.setHeaderTitle(this.selectedAccount.getJid().asBareJid().toString()); } @Override @@ -297,7 +292,7 @@ public class ManageAccountActivity extends XmppActivity private void publishAvatar(Account account) { Intent intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class); - intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toString()); startActivity(intent); } @@ -357,7 +352,8 @@ public class ManageAccountActivity extends XmppActivity Log.d( Config.LOGTAG, account.getJid().asBareJid() - + ": quick start disabled. account will regain this capability on the next connect"); + + ": quick start disabled. account will regain this capability on the" + + " next connect"); } if (!xmppConnectionService.updateAccount(account)) { Toast.makeText(this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show(); diff --git a/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java b/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java index b2a40976c503c4b744f83e0562092bf85dbb2f62..514d0ccfdfd8afb106d965cc6385fecab84e71a3 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java @@ -1,5 +1,8 @@ package eu.siacs.conversations.ui; +import static eu.siacs.conversations.utils.PermissionUtils.allGranted; +import static eu.siacs.conversations.utils.PermissionUtils.writeGranted; + import android.Manifest; import android.content.ActivityNotFoundException; import android.content.Intent; @@ -12,14 +15,10 @@ import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.databinding.DataBindingUtil; - -import java.util.Arrays; -import java.util.List; - +import com.google.common.base.Strings; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityWelcomeBinding; @@ -30,11 +29,8 @@ import eu.siacs.conversations.utils.InstallReferrerUtils; import eu.siacs.conversations.utils.SignupUtils; import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.Jid; - -import static eu.siacs.conversations.utils.PermissionUtils.allGranted; -import static eu.siacs.conversations.utils.PermissionUtils.writeGranted; - -import com.google.common.base.Strings; +import java.util.Arrays; +import java.util.List; public class WelcomeActivity extends XmppActivity implements XmppConnectionService.OnAccountCreated, KeyChainAliasCallback { @@ -196,7 +192,7 @@ public class WelcomeActivity extends XmppActivity @Override public void onAccountCreated(final Account account) { final Intent intent = new Intent(this, EditAccountActivity.class); - intent.putExtra("jid", account.getJid().asBareJid().toEscapedString()); + intent.putExtra("jid", account.getJid().asBareJid().toString()); intent.putExtra("init", true); addInviteUri(intent); startActivity(intent); diff --git a/src/conversations/java/eu/siacs/conversations/utils/ProvisioningUtils.java b/src/conversations/java/eu/siacs/conversations/utils/ProvisioningUtils.java index 593291d95374f4be2f1722b8c46a03fe34ed28dc..fdfe28dbb784ad05a6465c3bcea989192ea83495 100644 --- a/src/conversations/java/eu/siacs/conversations/utils/ProvisioningUtils.java +++ b/src/conversations/java/eu/siacs/conversations/utils/ProvisioningUtils.java @@ -3,15 +3,13 @@ package eu.siacs.conversations.utils; import android.app.Activity; import android.content.Intent; import android.widget.Toast; - -import java.util.List; - import eu.siacs.conversations.R; import eu.siacs.conversations.entities.AccountConfiguration; import eu.siacs.conversations.persistance.DatabaseBackend; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.EditAccountActivity; import eu.siacs.conversations.xmpp.Jid; +import java.util.List; public class ProvisioningUtils { @@ -20,7 +18,8 @@ public class ProvisioningUtils { try { accountConfiguration = AccountConfiguration.parse(json); } catch (final IllegalArgumentException e) { - Toast.makeText(activity, R.string.improperly_formatted_provisioning, Toast.LENGTH_LONG).show(); + Toast.makeText(activity, R.string.improperly_formatted_provisioning, Toast.LENGTH_LONG) + .show(); return; } final Jid jid = accountConfiguration.getJid(); @@ -31,13 +30,12 @@ public class ProvisioningUtils { } final Intent serviceIntent = new Intent(activity, XmppConnectionService.class); serviceIntent.setAction(XmppConnectionService.ACTION_PROVISION_ACCOUNT); - serviceIntent.putExtra("address", jid.asBareJid().toEscapedString()); + serviceIntent.putExtra("address", jid.asBareJid().toString()); serviceIntent.putExtra("password", accountConfiguration.password); Compatibility.startService(activity, serviceIntent); final Intent intent = new Intent(activity, EditAccountActivity.class); - intent.putExtra("jid", jid.asBareJid().toEscapedString()); + intent.putExtra("jid", jid.asBareJid().toString()); intent.putExtra("init", true); activity.startActivity(intent); } - } diff --git a/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java b/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java index fb088234a24e10ea315bafebd910e28df9caa270..b8123947f98b90e73ef230b50785f28eceefa369 100644 --- a/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java +++ b/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java @@ -2,7 +2,6 @@ package eu.siacs.conversations.utils; import android.app.Activity; import android.content.Intent; - import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.XmppConnectionService; @@ -21,13 +20,14 @@ public class SignupUtils { return true; } - public static Intent getTokenRegistrationIntent(final Activity activity, Jid jid, String preAuth) { + public static Intent getTokenRegistrationIntent( + final Activity activity, Jid jid, String preAuth) { final Intent intent = new Intent(activity, MagicCreateActivity.class); if (jid.isDomainJid()) { - intent.putExtra(MagicCreateActivity.EXTRA_DOMAIN, jid.getDomain().toEscapedString()); + intent.putExtra(MagicCreateActivity.EXTRA_DOMAIN, jid.getDomain().toString()); } else { - intent.putExtra(MagicCreateActivity.EXTRA_DOMAIN, jid.getDomain().toEscapedString()); - intent.putExtra(MagicCreateActivity.EXTRA_USERNAME, jid.getEscapedLocal()); + intent.putExtra(MagicCreateActivity.EXTRA_DOMAIN, jid.getDomain().toString()); + intent.putExtra(MagicCreateActivity.EXTRA_USERNAME, jid.getLocal()); } intent.putExtra(MagicCreateActivity.EXTRA_PRE_AUTH, preAuth); return intent; @@ -55,7 +55,9 @@ public class SignupUtils { intent = new Intent(activity, EditAccountActivity.class); intent.putExtra("jid", pendingAccount.getJid().asBareJid().toString()); if (!pendingAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) { - intent.putExtra(EditAccountActivity.EXTRA_FORCE_REGISTER, pendingAccount.isOptionSet(Account.OPTION_REGISTER)); + intent.putExtra( + EditAccountActivity.EXTRA_FORCE_REGISTER, + pendingAccount.isOptionSet(Account.OPTION_REGISTER)); } } else { if (service.getAccounts().size() == 0) { @@ -74,4 +76,4 @@ public class SignupUtils { intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); return intent; } -} \ No newline at end of file +} diff --git a/src/conversations/res/values-be/strings.xml b/src/conversations/res/values-be/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..a6b3daec9354f9ae75cdf8d94a67446c6227dd96 --- /dev/null +++ b/src/conversations/res/values-be/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/DowngradeProtection.java b/src/main/java/eu/siacs/conversations/crypto/sasl/DowngradeProtection.java index 6daaa8809398e6f4aa2c7d311ed894fc0ae05299..a1934c9e0d3a791aa73f4afb06bff2f0bd4df657 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/DowngradeProtection.java +++ b/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 mechanisms; public final ImmutableList 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) { diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/External.java b/src/main/java/eu/siacs/conversations/crypto/sasl/External.java index 6aba413a5682c9a544797aac797bec6ee6fa35e3..2e8adf1892d5332340e2a9fb28872223807f34df 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/External.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/External.java @@ -1,10 +1,8 @@ package eu.siacs.conversations.crypto.sasl; import android.util.Base64; - -import javax.net.ssl.SSLSocket; - import eu.siacs.conversations.entities.Account; +import javax.net.ssl.SSLSocket; public class External extends SaslMechanism { @@ -27,6 +25,6 @@ public class External extends SaslMechanism { @Override public String getClientFirstMessage(final SSLSocket sslSocket) { return Base64.encodeToString( - account.getJid().asBareJid().toEscapedString().getBytes(), Base64.NO_WRAP); + account.getJid().asBareJid().toString().getBytes(), Base64.NO_WRAP); } } diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java index 97ae1600ecfe8a95d4d34b7fc8253174110f3b84..0ee9b879c40fce893ab391d1182e727c8e9a86f7 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java +++ b/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"); } } diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java index 0cd880c85c7c82e4211a49a400216aa9988dc165..07974dab3af466f77c5dce788ce8e152ac0d58c1 100644 --- a/src/main/java/eu/siacs/conversations/entities/Account.java +++ b/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 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 fingerprints = this.getFingerprints(); String uri = "https://conversations.im/i/" - + XmppUri.lameUrlEncode(this.getJid().asBareJid().toEscapedString()); - if (fingerprints.size() > 0) { - return XmppUri.getFingerprintUri(uri, fingerprints, '&'); - } else { + + XmppUri.lameUrlEncode(this.getJid().asBareJid().toString()); + if (fingerprints.isEmpty()) { return uri; + } else { + return XmppUri.getFingerprintUri(uri, fingerprints, '&'); } } diff --git a/src/main/java/eu/siacs/conversations/entities/Bookmark.java b/src/main/java/eu/siacs/conversations/entities/Bookmark.java index 37467abcd04abeddb0eae00dc16652cc9c707c29..cef2b653eefc9f99dbb97bb6d2988835585c80eb 100644 --- a/src/main/java/eu/siacs/conversations/entities/Bookmark.java +++ b/src/main/java/eu/siacs/conversations/entities/Bookmark.java @@ -1,13 +1,15 @@ package eu.siacs.conversations.entities; import android.content.Context; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; - +import eu.siacs.conversations.utils.StringUtils; +import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xml.Namespace; +import eu.siacs.conversations.xmpp.Jid; import java.lang.ref.WeakReference; import java.util.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; - 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 parseFromStorage(Element storage, Account account) { - if (storage == null) { - return Collections.emptyMap(); - } - final HashMap 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 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 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; + 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 parseFromStorage(Element storage, Account account) { + if (storage == null) { + return Collections.emptyMap(); + } + final HashMap 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 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 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(); + } } diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java index 7c6f9ec9d5609c65022e1d94d3f7d8ce2b31fa8c..7520ed2b5ae341bcaf4919a801d579d0c8ac5d7a 100644 --- a/src/main/java/eu/siacs/conversations/entities/Contact.java +++ b/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 diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 1e790acad03f1b9ee38dffa1b18d465fbf205f6d..c920481fca33b01246be5176060d80df3fa587a3 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/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)) { diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 31db0cb06d55643d755cf31283d72680fc19a9bb..112f3c40fab9c80f97d63d0a741c296ecfa4f2c8 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/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; } diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index 24b0d0e7642ab42adb356204cefc60dc4ba7ee2e..2643b8d8dcb5a32ce93b443fbdbbfe2d6ab7ce33 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/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 forms = serviceDiscoveryResult == null ? Collections.emptyList() : serviceDiscoveryResult.forms; + final List 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 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 findUsers(final Collection reactions) { final ImmutableList.Builder 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 getUsers(int max) { - ArrayList subset = new ArrayList<>(); - HashSet jids = new HashSet<>(); - jids.add(account.getJid().asBareJid()); + public List getUsers(final int max) { + final ArrayList subset = new ArrayList<>(); + final HashSet 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 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 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 { 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() { diff --git a/src/main/java/eu/siacs/conversations/entities/RawBlockable.java b/src/main/java/eu/siacs/conversations/entities/RawBlockable.java index 664a6a1c938a705509f5ade4b927141dc5b83e6c..97f63d99cfe6eb4a13bb224687a28f0b3309dda4 100644 --- a/src/main/java/eu/siacs/conversations/entities/RawBlockable.java +++ b/src/main/java/eu/siacs/conversations/entities/RawBlockable.java @@ -2,14 +2,12 @@ package eu.siacs.conversations.entities; import android.content.Context; import android.text.TextUtils; - +import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.xmpp.Jid; import java.util.Collections; import java.util.List; import java.util.Locale; -import eu.siacs.conversations.utils.UIHelper; -import eu.siacs.conversations.xmpp.Jid; - public class RawBlockable implements ListItem, Blockable { private final Account account; @@ -40,7 +38,7 @@ public class RawBlockable implements ListItem, Blockable { if (jid.isFullJid()) { return jid.getResource(); } else { - return jid.toEscapedString(); + return jid.toString(); } } @@ -62,7 +60,7 @@ public class RawBlockable implements ListItem, Blockable { needle = needle.toLowerCase(Locale.US).trim(); String[] parts = needle.split("\\s+"); for (String part : parts) { - if (!jid.toEscapedString().contains(part)) { + if (!jid.toString().contains(part)) { return false; } } @@ -76,7 +74,7 @@ public class RawBlockable implements ListItem, Blockable { @Override public int getAvatarBackgroundColor() { - return UIHelper.getColorForName(jid.toEscapedString()); + return UIHelper.getColorForName(jid.toString()); } @Override @@ -86,7 +84,6 @@ public class RawBlockable implements ListItem, Blockable { @Override public int compareTo(ListItem o) { - return this.getDisplayName().compareToIgnoreCase( - o.getDisplayName()); + return this.getDisplayName().compareToIgnoreCase(o.getDisplayName()); } -} \ No newline at end of file +} diff --git a/src/main/java/eu/siacs/conversations/entities/Reaction.java b/src/main/java/eu/siacs/conversations/entities/Reaction.java index ce8ac5044163cdf8c842b723b95d8e7b31fa651c..84dda0bdaedd5fcb5efb05c46381faaa08482f2b 100644 --- a/src/main/java/eu/siacs/conversations/entities/Reaction.java +++ b/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"); } diff --git a/src/main/java/eu/siacs/conversations/entities/Room.java b/src/main/java/eu/siacs/conversations/entities/Room.java index 9e1d61fc3dc904ebeef044edd4552c59cb5dc3eb..c702c3189bfde4306b067b8448b718d6ab173731 100644 --- a/src/main/java/eu/siacs/conversations/entities/Room.java +++ b/src/main/java/eu/siacs/conversations/entities/Room.java @@ -3,7 +3,6 @@ package eu.siacs.conversations.entities; import com.google.common.base.Objects; import com.google.common.base.Strings; import com.google.common.collect.ComparisonChain; - import eu.siacs.conversations.services.AvatarService; import eu.siacs.conversations.utils.LanguageUtils; import eu.siacs.conversations.utils.UIHelper; @@ -25,9 +24,7 @@ public class Room implements AvatarService.Avatarable, Comparable { this.nusers = nusers; } - public Room() { - - } + public Room() {} public String getName() { return name; @@ -52,7 +49,7 @@ public class Room implements AvatarService.Avatarable, Comparable { @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 { 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 { 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 { .compare(Strings.nullToEmpty(address), Strings.nullToEmpty(o.address)) .result(); } -} \ No newline at end of file +} diff --git a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java index e851d247ef7e16dc09c8df564cab6ad0adb21c4f..d041ce577edc4a8595b95b0bfef75373174c1d0c 100644 --- a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java +++ b/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" diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 219547eee4ec565be71edc21a14c38ca00d1f84e..d648536699267bdfa7b68d10b719b1c777ef1cdb 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/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 preKeyRecords, final int deviceId, Bundle publishOptions) { + public Iq publishBundles( + final SignedPreKeyRecord signedPreKeyRecord, + final IdentityKey identityKey, + final Set 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; } diff --git a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java index 3530097886f485c99a553082de826917abe8a6e0..3b40c82b19a9392dff925b9659706ddcc6bf54c8 100644 --- a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java +++ b/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 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 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 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 errorNames) { - if (errorNames.size() > 0) { - return errorNames.get(0)+'\u001f'; - } - return ""; - } - - private static List orderedElementNames(List children) { - List 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 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 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 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 errorNames) { + if (errorNames.size() > 0) { + return errorNames.get(0) + '\u001f'; + } + return ""; + } + + private static List orderedElementNames(List children) { + List names = new ArrayList<>(); + for (Element child : children) { + final String name = child.getName(); + if (name != null && !name.equals("text")) { + if ("urn:ietf:params:xml:ns:xmpp-stanzas".equals(child.getNamespace())) { + names.add(name); + } else { + names.add(0, name); + } + } + } + return names; + } } diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java index 95b239802f50b767fe0c3fb23a71f21ea234095a..c562da2ebc601ffa1a9c2dac393bb58c1846263a 100644 --- a/src/main/java/eu/siacs/conversations/parser/IqParser.java +++ b/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 { public IqParser(final XmppConnectionService service, final Account account) { @@ -95,8 +103,7 @@ public class IqParser extends AbstractParser implements Consumer { 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 { } 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 { 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 { Integer id = Integer.valueOf(device.getAttribute("id")); deviceIds.add(id); } catch (NumberFormatException e) { - Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Encountered invalid node in PEP (" + e.getMessage() + "):" + device.toString() + ", skipping..."); + Log.e( + Config.LOGTAG, + AxolotlService.LOGPREFIX + + " : " + + "Encountered invalid node in PEP (" + + e.getMessage() + + "):" + + device.toString() + + ", skipping..."); } } } @@ -211,7 +234,12 @@ public class IqParser extends AbstractParser implements Consumer { 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 { 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 { 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 { Map preKeyRecords = new HashMap<>(); Element item = getItem(packet); if (item == null) { - Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Couldn't find in bundle IQ packet: " + packet); + Log.d( + Config.LOGTAG, + AxolotlService.LOGPREFIX + + " : " + + "Couldn't find in bundle IQ packet: " + + packet); return null; } final Element bundleElement = item.findChild("bundle"); @@ -255,12 +295,22 @@ public class IqParser extends AbstractParser implements Consumer { } final Element prekeysElement = bundleElement.findChild("prekeys"); if (prekeysElement == null) { - Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Couldn't find in bundle IQ packet: " + packet); + Log.d( + Config.LOGTAG, + AxolotlService.LOGPREFIX + + " : " + + "Couldn't find 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 { 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 { public static Pair 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 { 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 { || 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 preKeys(final Iq preKeys) { @@ -343,8 +418,7 @@ public class IqParser extends AbstractParser implements Consumer { 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 { 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 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 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 { // 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 { 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 items = packet.findChild("unblock", Namespace.BLOCKING).getChildren(); + final Collection 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 { final Collection 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 { } 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 { 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 { } } } - } diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index cc2dc8bd975d5eb90fd9f3c75a80f0123057c496..7c19680cc75242b9ccd6139d2e301a6f07714b8a 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/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 { +public class MessageParser extends AbstractParser + implements Consumer { - 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 JINGLE_MESSAGE_ELEMENT_NAMES = Arrays.asList("accept", "propose", "proceed", "reject", "retract", "ringing", "finish"); @@ -86,7 +88,8 @@ public class MessageParser extends AbstractParser implements Consumer 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 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 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 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 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); } @@ -1123,7 +1398,8 @@ public class MessageParser extends AbstractParser implements Consumer(reactions.getReactions()); - newReactions.removeAll(message.getReactions().stream().filter(r -> occupantId.equals(r.occupantId)).map(r -> r.reaction).collect(Collectors.toList())); - final var combinedReactions = - Reaction.withOccupantId( - message.getReactions(), - reactions.getReactions(), - isReceived, - counterpart, - null, - occupantId, - message.getRemoteMsgId()); - message.setReactions(combinedReactions); - mXmppConnectionService.updateMessage(message, false); - if (isReceived) mXmppConnectionService.getNotificationService().push(message, counterpart, occupantId, newReactions); - } else { - Log.d(Config.LOGTAG, "message with id " + reactingTo + " not found"); - } - } else { - Log.d( - Config.LOGTAG, - "received reaction in channel w/o occupant ids. ignoring"); - } - } else if (conversation.getMode() == Conversational.MODE_SINGLE) { + private void processReactions( + final Reactions reactions, + final Conversation conversation, + final boolean isTypeGroupChat, + final OccupantId occupant, + final Jid counterpart, + final Jid mucTrueCounterPart, + final int status, + final im.conversations.android.xmpp.model.stanza.Message packet) { + final String reactingTo = reactions.getId(); + if (conversation != null && reactingTo != null) { + if (isTypeGroupChat && conversation.getMode() == Conversational.MODE_MULTI) { + final var mucOptions = conversation.getMucOptions(); + final var occupantId = occupant == null ? null : occupant.getId(); + if (occupantId != null) { + final boolean isReceived = !mucOptions.isSelf(occupantId); final Message message; - final var inMemoryMessage = - conversation.findMessageWithUuidOrRemoteId(reactingTo); + final var inMemoryMessage = conversation.findMessageWithServerMsgId(reactingTo); if (inMemoryMessage != null) { message = inMemoryMessage; } else { message = - mXmppConnectionService.databaseBackend.getMessageWithUuidOrRemoteId( + mXmppConnectionService.databaseBackend.getMessageWithServerMsgId( conversation, reactingTo); } - final boolean isReceived; - final Jid reactionFrom; - if (packet.fromAccount(account)) { - isReceived = false; - reactionFrom = account.getJid().asBareJid(); - } else { - isReceived = true; - reactionFrom = counterpart; - } - packet.fromAccount(account); if (message != null) { final var newReactions = new HashSet<>(reactions.getReactions()); - newReactions.removeAll(message.getReactions().stream().filter(r -> reactionFrom.equals(r.from)).map(r -> r.reaction).collect(Collectors.toList())); + newReactions.removeAll(message.getReactions().stream().filter(r -> occupantId.equals(r.occupantId)).map(r -> r.reaction).collect(Collectors.toList())); final var combinedReactions = - Reaction.withFrom( + Reaction.withOccupantId( message.getReactions(), reactions.getReactions(), isReceived, - reactionFrom, + counterpart, + mucTrueCounterPart, + occupantId, message.getRemoteMsgId()); message.setReactions(combinedReactions); mXmppConnectionService.updateMessage(message, false); - if (status < Message.STATUS_SEND) mXmppConnectionService.getNotificationService().push(message, counterpart, null, newReactions); + if (isReceived) mXmppConnectionService.getNotificationService().push(message, counterpart, occupantId, newReactions); } else { Log.d(Config.LOGTAG, "message with id " + reactingTo + " not found"); } + } else { + Log.d(Config.LOGTAG, "received reaction in channel w/o occupant ids. ignoring"); } - } - } - - final Element event = original.findChild("event", "http://jabber.org/protocol/pubsub#event"); - if (event != null && InvalidJid.hasValidFrom(original) && original.getFrom().isBareJid()) { - if (event.hasChild("items")) { - parseEvent(event, original.getFrom(), account); - } else if (event.hasChild("delete")) { - parseDeleteEvent(event, original.getFrom(), account); - } else if (event.hasChild("purge")) { - parsePurgeEvent(event, original.getFrom(), account); - } - } - - final String nick = packet.findChildContent("nick", Namespace.NICK); - if (nick != null && InvalidJid.hasValidFrom(original)) { - if (mXmppConnectionService.isMuc(account, from)) { - return; - } - final Contact contact = account.getRoster().getContact(from); - if (contact.setPresenceName(nick)) { - mXmppConnectionService.syncRoster(account); - mXmppConnectionService.getAvatarService().clear(contact); + } else { + final Message message; + final var inMemoryMessage = conversation.findMessageWithUuidOrRemoteId(reactingTo); + if (inMemoryMessage != null) { + message = inMemoryMessage; + } else { + message = + mXmppConnectionService.databaseBackend.getMessageWithUuidOrRemoteId( + conversation, reactingTo); + } + if (message == null) { + Log.d(Config.LOGTAG, "message with id " + reactingTo + " not found"); + return; + } + final boolean isReceived; + final Jid reactionFrom; + if (conversation.getMode() == Conversational.MODE_MULTI) { + Log.d(Config.LOGTAG, "received reaction as MUC PM. triggering validation"); + final var mucOptions = conversation.getMucOptions(); + final var occupantId = occupant == null ? null : occupant.getId(); + if (occupantId == null) { + Log.d( + Config.LOGTAG, + "received reaction via PM channel w/o occupant ids. ignoring"); + return; + } + isReceived = !mucOptions.isSelf(occupantId); + if (isReceived) { + reactionFrom = counterpart; + } else { + if (!occupantId.equals(message.getOccupantId())) { + Log.d( + Config.LOGTAG, + "reaction received via MUC PM did not pass validation"); + return; + } + reactionFrom = account.getJid().asBareJid(); + } + } else { + if (packet.fromAccount(account)) { + isReceived = false; + reactionFrom = account.getJid().asBareJid(); + } else { + isReceived = true; + reactionFrom = counterpart; + } + } + final var newReactions = new HashSet<>(reactions.getReactions()); + newReactions.removeAll(message.getReactions().stream().filter(r -> reactionFrom.equals(r.from)).map(r -> r.reaction).collect(Collectors.toList())); + final var combinedReactions = + Reaction.withFrom( + message.getReactions(), + reactions.getReactions(), + isReceived, + reactionFrom, + message.getRemoteMsgId()); + message.setReactions(combinedReactions); + mXmppConnectionService.updateMessage(message, false); + if (status < Message.STATUS_SEND) mXmppConnectionService.getNotificationService().push(message, counterpart, null, newReactions); } } } - private static Pair getForwardedMessagePacket(final im.conversations.android.xmpp.model.stanza.Message original, Class clazz) { + private static Pair + getForwardedMessagePacket( + final im.conversations.android.xmpp.model.stanza.Message original, + Class clazz) { final var extension = original.getExtension(clazz); final var forwarded = extension == null ? null : extension.getExtension(Forwarded.class); if (forwarded == null) { @@ -1469,36 +1840,51 @@ public class MessageParser extends AbstractParser implements Consumer(forwardedMessage,timestamp); + return new Pair<>(forwardedMessage, timestamp); } - private static Pair getForwardedMessagePacket(final im.conversations.android.xmpp.model.stanza.Message original, final String name, final String namespace) { + private static Pair + getForwardedMessagePacket( + final im.conversations.android.xmpp.model.stanza.Message original, + final String name, + final String namespace) { final Element wrapper = original.findChild(name, namespace); - final var forwardedElement = wrapper == null ? null : wrapper.findChild("forwarded",Namespace.FORWARD); + final var forwardedElement = + wrapper == null ? null : wrapper.findChild("forwarded", Namespace.FORWARD); if (forwardedElement instanceof Forwarded forwarded) { final Long timestamp = AbstractParser.parseTimestamp(forwarded, null); final var forwardedMessage = forwarded.getMessage(); if (forwardedMessage == null) { return null; } - return new Pair<>(forwardedMessage,timestamp); + return new Pair<>(forwardedMessage, timestamp); } return null; } - private void dismissNotification(Account account, Jid counterpart, MessageArchiveService.Query query, final String id) { - final Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid()); + private void dismissNotification( + Account account, Jid counterpart, MessageArchiveService.Query query, final String id) { + final Conversation conversation = + mXmppConnectionService.find(account, counterpart.asBareJid()); if (conversation != null && (query == null || query.isCatchup())) { final String displayableId = conversation.findMostRecentRemoteDisplayableId(); if (displayableId != null && displayableId.equals(id)) { mXmppConnectionService.markRead(conversation); } else { - Log.w(Config.LOGTAG, account.getJid().asBareJid() + ": received dismissing display marker that did not match our last id in that conversation"); + Log.w( + Config.LOGTAG, + account.getJid().asBareJid() + + ": received dismissing display marker that did not match our last" + + " id in that conversation"); } } } - private void processMessageReceipts(final Account account, final im.conversations.android.xmpp.model.stanza.Message packet, final String remoteMsgId, MessageArchiveService.Query query) { + private void processMessageReceipts( + final Account account, + final im.conversations.android.xmpp.model.stanza.Message packet, + final String remoteMsgId, + MessageArchiveService.Query query) { final boolean markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0"); final boolean request = packet.hasChild("request", "urn:xmpp:receipts"); if (query == null) { @@ -1510,11 +1896,15 @@ public class MessageParser extends AbstractParser implements Consumer 0) { - final var receipt = mXmppConnectionService.getMessageGenerator().received(account, - packet.getFrom(), - remoteMsgId, - receiptsNamespaces, - packet.getType()); + final var receipt = + mXmppConnectionService + .getMessageGenerator() + .received( + account, + packet.getFrom(), + remoteMsgId, + receiptsNamespaces, + packet.getType()); mXmppConnectionService.sendMessagePacket(account, receipt); } } else if (query.isCatchup()) { @@ -1525,8 +1915,15 @@ public class MessageParser extends AbstractParser implements Consumer { +public class PresenceParser extends AbstractParser + implements Consumer { 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 tileUserBefore = mucOptions.getUsers(5); - processConferencePresence(packet, conversation); - final List 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 tileUserBefore = mucOptions.getUsers(5); + processConferencePresence(packet, conversation); + final List 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= " + "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 deletePushTargets() { final SQLiteDatabase sqLiteDatabase = getReadableDatabase(); final ImmutableList.Builder builder = new ImmutableList.Builder<>(); - try (final Cursor cursor = sqLiteDatabase.query("push",new String[]{"application","instance"},null,null,null,null,null)) { + try (final Cursor cursor = + sqLiteDatabase.query( + "push", + new String[] {"application", "instance"}, + null, + null, + null, + null, + null)) { if (cursor != null && cursor.moveToFirst()) { - builder.add(new PushTarget( - cursor.getString(cursor.getColumnIndexOrThrow("application")), - cursor.getString(cursor.getColumnIndexOrThrow("instance")))); + builder.add( + new PushTarget( + cursor.getString(cursor.getColumnIndexOrThrow("application")), + cursor.getString(cursor.getColumnIndexOrThrow("instance")))); } } catch (final Exception e) { - Log.d(Config.LOGTAG,"unable to retrieve push targets",e); + Log.d(Config.LOGTAG, "unable to retrieve push targets", e); return builder.build(); } - sqLiteDatabase.delete("push",null,null); + sqLiteDatabase.delete("push", null, null); return builder.build(); } @@ -149,9 +157,10 @@ public class UnifiedPushDatabase extends SQLiteOpenHelper { final SQLiteDatabase sqLiteDatabase = getReadableDatabase(); try (final Cursor cursor = sqLiteDatabase.rawQuery( - "SELECT EXISTS(SELECT endpoint FROM push WHERE account = ? AND transport = ?)", + "SELECT EXISTS(SELECT endpoint FROM push WHERE account = ? AND transport =" + + " ?)", new String[] { - transport.account.getUuid(), transport.transport.toEscapedString() + transport.account.getUuid(), transport.transport.toString() })) { if (cursor != null && cursor.moveToFirst()) { return cursor.getInt(0) > 0; diff --git a/src/main/java/eu/siacs/conversations/services/AvatarService.java b/src/main/java/eu/siacs/conversations/services/AvatarService.java index 3b4d87aebfd41bff970c9b05d8435ac544592e54..8f9662e6a19ba5cff4946182c461a7919971c56b 100644 --- a/src/main/java/eu/siacs/conversations/services/AvatarService.java +++ b/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) { diff --git a/src/main/java/eu/siacs/conversations/services/CallIntegration.java b/src/main/java/eu/siacs/conversations/services/CallIntegration.java index b6efdd85185d6ee53a7ac08b1ee444c025b83256..01534fb08ce3d9b78401d0673af555def1182629 100644 --- a/src/main/java/eu/siacs/conversations/services/CallIntegration.java +++ b/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() { diff --git a/src/main/java/eu/siacs/conversations/services/CallIntegrationConnectionService.java b/src/main/java/eu/siacs/conversations/services/CallIntegrationConnectionService.java index facc616b098ffefe342adfa66a1685bab6624a98..fd849c0174d02f4f3ddc7f2da396991cbbf90faf 100644 --- a/src/main/java/eu/siacs/conversations/services/CallIntegrationConnectionService.java +++ b/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 = @@ -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"); } diff --git a/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java b/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java index e2a828ac63209592dabcce2b9c85c0096bc4e6c5..ccc02d36b476c50ecc24e3a982ec80fc6f9666ee 100644 --- a/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java +++ b/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java @@ -1,14 +1,10 @@ package eu.siacs.conversations.services; - import android.util.Log; - import androidx.annotation.NonNull; - import com.google.common.base.Strings; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; - import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Room; @@ -17,18 +13,7 @@ import eu.siacs.conversations.http.services.MuclumbusService; import eu.siacs.conversations.parser.IqParser; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.XmppConnection; - import im.conversations.android.xmpp.model.stanza.Iq; - -import okhttp3.OkHttpClient; -import okhttp3.ResponseBody; - -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; - import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -38,6 +23,13 @@ import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import okhttp3.OkHttpClient; +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; public class ChannelDiscoveryService { @@ -57,7 +49,8 @@ public class ChannelDiscoveryService { this.muclumbusService = null; return; } - final OkHttpClient.Builder builder = HttpConnectionManager.okHttpClient(service).newBuilder(); + final OkHttpClient.Builder builder = + HttpConnectionManager.okHttpClient(service).newBuilder(); if (service.useTorToConnect()) { builder.proxy(HttpConnectionManager.getProxy()); } @@ -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); } diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index 09b19a4a370e9ebb52544515b4c83ec3a2e0a113..f50a25ae02e889d39881bdccd973cd12ec609d75 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/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( diff --git a/src/main/java/eu/siacs/conversations/services/ShortcutService.java b/src/main/java/eu/siacs/conversations/services/ShortcutService.java index 547f8019a285ca88a9642cc0abfa9831132a94e3..1f286529cfd77f13afae0d2e5c2c13c1c89d21b3 100644 --- a/src/main/java/eu/siacs/conversations/services/ShortcutService.java +++ b/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) { diff --git a/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java b/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java index 92ae9d9ec668533d097680e333ac58bb4bc33248..24aaf1f4279b04abd32fbf3da615283c0dda3a32 100644 --- a/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java +++ b/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java @@ -10,10 +10,8 @@ import android.os.Messenger; import android.os.RemoteException; import android.preference.PreferenceManager; import android.util.Log; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.common.base.Optional; import com.google.common.base.Strings; import com.google.common.collect.Iterables; @@ -22,7 +20,6 @@ import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; @@ -34,7 +31,6 @@ import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.Jid; import im.conversations.android.xmpp.model.stanza.Iq; import im.conversations.android.xmpp.model.stanza.Presence; - import java.nio.charset.StandardCharsets; import java.text.ParseException; import java.util.List; @@ -92,7 +88,8 @@ public class UnifiedPushBroker { renewUnifiedPushEndpoints(null); } - public Optional renewUnifiedPushEndpoints(@Nullable final PushTargetMessenger pushTargetMessenger) { + public Optional renewUnifiedPushEndpoints( + @Nullable final PushTargetMessenger pushTargetMessenger) { final Optional 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 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 pushTargets) { + public void onSuccess(final List 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> deletePushTargets() { - return Futures.submit(() -> UnifiedPushDatabase.getInstance(service).deletePushTargets(),SCHEDULER); + return Futures.submit( + () -> UnifiedPushDatabase.getInstance(service).deletePushTargets(), SCHEDULER); } private void broadcastUnregistered(final List 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 pushTargets = - UnifiedPushDatabase.getInstance(service) - .getPushTargets(uuid, transport.toEscapedString()); + UnifiedPushDatabase.getInstance(service).getPushTargets(uuid, transport.toString()); return Iterables.tryFind( pushTargets, pt -> @@ -422,7 +429,8 @@ public class UnifiedPushBroker { service.sendBroadcast(updateIntent); } - private Intent endpointIntent(final String instance, final UnifiedPushDatabase.ApplicationEndpoint endpoint) { + private Intent endpointIntent( + final String instance, final UnifiedPushDatabase.ApplicationEndpoint endpoint) { final Intent intent = new Intent(UnifiedPushDistributor.ACTION_NEW_ENDPOINT); intent.setPackage(endpoint.application); intent.putExtra("token", instance); @@ -449,13 +457,12 @@ public class UnifiedPushBroker { return intent; } - public void rebroadcastEndpoint(final Messenger messenger, final String instance, final Transport transport) { + public void rebroadcastEndpoint( + final Messenger messenger, final String instance, final Transport transport) { final UnifiedPushDatabase unifiedPushDatabase = UnifiedPushDatabase.getInstance(service); final UnifiedPushDatabase.ApplicationEndpoint endpoint = unifiedPushDatabase.getEndpoint( - transport.account.getUuid(), - transport.transport.toEscapedString(), - instance); + transport.account.getUuid(), transport.transport.toString(), instance); if (endpoint != null) { sendEndpoint(messenger, instance, endpoint); } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 6bd5f2ca28483e7932b87fb8968153fbdc218cb1..a5b51bb43b4be6833d33710c9781b75d04dc66f6 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/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 members = conversation.getMucOptions().getMembers(true); + final var mucOptions = conversation.getMucOptions(); + List members = mucOptions.getMembers(true); if (success) { List 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); } diff --git a/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java b/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java index 73f6420a09bc45f9273297f7eda2aa947dd29687..8978fe0da61d3a8399b80e5f834ac756891598ea 100644 --- a/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java +++ b/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java @@ -2,13 +2,9 @@ package eu.siacs.conversations.ui; import android.view.View; import android.widget.Toast; - import androidx.annotation.StringRes; -import androidx.appcompat.app.AlertDialog; import androidx.databinding.DataBindingUtil; - import com.google.android.material.dialog.MaterialAlertDialogBuilder; - import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.DialogBlockContactBinding; import eu.siacs.conversations.entities.Blockable; @@ -17,43 +13,56 @@ import eu.siacs.conversations.ui.util.JidDialog; public final class BlockContactDialog { - public static void show(final XmppActivity xmppActivity, final Blockable blockable) { - show(xmppActivity, blockable, null); - } - public static void show(final XmppActivity xmppActivity, final Blockable blockable, final String serverMsgId) { - final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(xmppActivity); - final boolean isBlocked = blockable.isBlocked(); - builder.setNegativeButton(R.string.cancel, null); - DialogBlockContactBinding binding = DataBindingUtil.inflate(xmppActivity.getLayoutInflater(), R.layout.dialog_block_contact, null, false); - final boolean reporting = blockable.getAccount().getXmppConnection().getFeatures().spamReporting(); - if (reporting && !isBlocked) { - binding.reportSpam.setVisibility(View.VISIBLE); - if (serverMsgId != null) { - binding.reportSpam.setChecked(true); - binding.reportSpam.setEnabled(false); - } else { - binding.reportSpam.setEnabled(true); - } - } else { - binding.reportSpam.setVisibility(View.GONE); - } - builder.setView(binding.getRoot()); + public static void show(final XmppActivity xmppActivity, final Blockable blockable) { + show(xmppActivity, blockable, null); + } - 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(); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java b/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java index 20eac8bc302ff21ff47742eaaa4427d132549d86..7d55e33a67ff3bf44c96a0806b9451b7dcb68f7c 100644 --- a/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java @@ -3,12 +3,8 @@ package eu.siacs.conversations.ui; import android.os.Bundle; import android.text.Editable; import android.widget.Toast; - import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; - -import java.util.Collections; - import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Blockable; @@ -17,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(); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java b/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java index b5a7e6d4ceadfaaabc958e00c80aed7a92825889..f4bea10d84deae9db682b0435c803264e4f4fe41 100644 --- a/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java +++ b/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( diff --git a/src/main/java/eu/siacs/conversations/ui/ChooseAccountForProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/ChooseAccountForProfilePictureActivity.java index 71662589ca3739f9a6a86ad83cefb828488fc6d5..ff60e6419b2abde89bbc53b6fce4417bb90bef7b 100644 --- a/src/main/java/eu/siacs/conversations/ui/ChooseAccountForProfilePictureActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ChooseAccountForProfilePictureActivity.java @@ -4,14 +4,11 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.widget.Toast; - import androidx.databinding.DataBindingUtil; - import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityManageAccountsBinding; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.ui.adapter.AccountAdapter; - import java.util.ArrayList; import java.util.List; @@ -29,16 +26,18 @@ public class ChooseAccountForProfilePictureActivity extends XmppActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - final ActivityManageAccountsBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_manage_accounts); + final ActivityManageAccountsBinding binding = + DataBindingUtil.setContentView(this, R.layout.activity_manage_accounts); Activities.setStatusAndNavigationBarColors(this, binding.getRoot()); setSupportActionBar(binding.toolbar); configureActionBar(getSupportActionBar(), false); this.mAccountAdapter = new AccountAdapter(this, accountList, false); binding.accountList.setAdapter(this.mAccountAdapter); - binding.accountList.setOnItemClickListener((arg0, view, position, arg3) -> { - final Account account = accountList.get(position); - goToProfilePictureActivity(account); - }); + binding.accountList.setOnItemClickListener( + (arg0, view, position, arg3) -> { + final Account account = accountList.get(position); + goToProfilePictureActivity(account); + }); } @Override @@ -58,7 +57,7 @@ public class ChooseAccountForProfilePictureActivity extends XmppActivity { private void loadEnabledAccounts() { accountList.clear(); - for(Account account : xmppConnectionService.getAccounts()) { + for (Account account : xmppConnectionService.getAccounts()) { if (account.isEnabled()) { accountList.add(account); } @@ -70,13 +69,17 @@ public class ChooseAccountForProfilePictureActivity extends XmppActivity { final Uri uri = startIntent == null ? null : startIntent.getData(); if (uri != null) { Intent intent = new Intent(this, PublishProfilePictureActivity.class); - intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toString()); intent.setData(uri); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); try { startActivity(intent); } catch (SecurityException e) { - Toast.makeText(this, R.string.sharing_application_not_grant_permission, Toast.LENGTH_SHORT).show(); + Toast.makeText( + this, + R.string.sharing_application_not_grant_permission, + Toast.LENGTH_SHORT) + .show(); return; } } diff --git a/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java b/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java index 1198d86dd7ae5ef648da2b4c6ac4e3a769799535..1344e40d6c8b6290d63b2f4c3555482709416c16 100644 --- a/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java @@ -16,23 +16,12 @@ import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AdapterView; import android.widget.ListView; import android.widget.TextView; - import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.appcompat.app.ActionBar; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; - import com.google.common.base.Strings; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; @@ -44,8 +33,15 @@ import eu.siacs.conversations.ui.util.ActivityResult; import eu.siacs.conversations.ui.util.PendingItem; import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.Jid; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; -public class ChooseContactActivity extends AbstractSearchableListItemActivity implements MultiChoiceModeListener, AdapterView.OnItemClickListener { +public class ChooseContactActivity extends AbstractSearchableListItemActivity + implements MultiChoiceModeListener, AdapterView.OnItemClickListener { public static final String EXTRA_TITLE_RES_ID = "extra_title_res_id"; public static final String EXTRA_GROUP_CHAT_NAME = "extra_group_chat_name"; public static final String EXTRA_SELECT_MULTIPLE = "extra_select_multiple"; @@ -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(); 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); diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index 8843c3cb2670ed8125dad6cb39a01c50ac21035a..4c731d9d97b832b977bdbc812316a5eae8a01f84 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/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 diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index 8f66f34038330b3c199128a2bef2609bc7ea7e73..e81ceba0a6b1bbef777829fcd593ac6f9fd0cc19 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/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); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 1867735579571a3b5fbdfda5a4af0081398e5e2c..8226c6e81e72261d856e851960b0282ef5c708ae 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/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; diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java index 7546aaf41d617f96cb919224d7c869dc2f1e3b7c..23ebc7bd7684ece2fd5640ec2d7b23592ae59942 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java @@ -29,12 +29,13 @@ package eu.siacs.conversations.ui; +import static androidx.recyclerview.widget.ItemTouchHelper.LEFT; +import static androidx.recyclerview.widget.ItemTouchHelper.RIGHT; + import android.app.Activity; -import android.app.AlertDialog; import android.app.Fragment; import android.content.Intent; import android.graphics.Canvas; -import android.graphics.Paint; import android.os.Bundle; import android.util.Log; import android.view.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 conversations = new ArrayList<>(); - private final PendingItem swipedConversation = new PendingItem<>(); - private final PendingItem 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 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 = 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 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 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 conversations = new ArrayList<>(); + private final PendingItem swipedConversation = new PendingItem<>(); + private final PendingItem 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 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 = 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 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 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); + } + } } diff --git a/src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java b/src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java index b20db451d74501c0cfc5c4fac6895b17fc356029..b12aba750cb0accb3a0ace23dccf6000425ba759 100644 --- a/src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java +++ b/src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java @@ -11,18 +11,11 @@ import android.text.TextWatcher; import android.view.View; import android.widget.AdapterView; import android.widget.Button; - import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.databinding.DataBindingUtil; import androidx.fragment.app.DialogFragment; - import com.google.android.material.dialog.MaterialAlertDialogBuilder; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.DialogCreatePublicChannelBinding; import eu.siacs.conversations.entities.Account; @@ -33,10 +26,14 @@ import eu.siacs.conversations.ui.util.DelayedHintHelper; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.XmppConnection; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; public class CreatePublicChannelDialog extends DialogFragment implements OnBackendConnected { - private static final char[] FORBIDDEN = new char[]{'\u0022','&','\'','/',':','<','>','@'}; + private static final char[] FORBIDDEN = + new char[] {'\u0022', '&', '\'', '/', ':', '<', '>', '@'}; private static final String ACCOUNTS_LIST_KEY = "activated_accounts_list"; private CreatePublicChannelDialogListener mListener; @@ -62,48 +59,56 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - jidWasModified = savedInstanceState != null && savedInstanceState.getBoolean("jid_was_modified_false", false); - nameEntered = savedInstanceState != null && savedInstanceState.getBoolean("name_entered", false); - final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity()); + jidWasModified = + savedInstanceState != null + && savedInstanceState.getBoolean("jid_was_modified_false", false); + nameEntered = + savedInstanceState != null && savedInstanceState.getBoolean("name_entered", false); + final MaterialAlertDialogBuilder builder = + new MaterialAlertDialogBuilder(requireActivity()); builder.setTitle(R.string.create_public_channel); - final DialogCreatePublicChannelBinding binding = DataBindingUtil.inflate(getActivity().getLayoutInflater(), R.layout.dialog_create_public_channel, null, false); - binding.account.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - updateJidSuggestion(binding); - } - - @Override - public void onNothingSelected(AdapterView parent) { - - } - }); - binding.jid.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - - } - - @Override - public void afterTextChanged(Editable s) { - if (skipTetxWatcher) { - return; - } - if (jidWasModified) { - jidWasModified = !TextUtils.isEmpty(s); - } else { - jidWasModified = !s.toString().equals(getJidSuggestion(binding)); - } - } - }); - updateInputs(binding,false); + final DialogCreatePublicChannelBinding binding = + DataBindingUtil.inflate( + getActivity().getLayoutInflater(), + R.layout.dialog_create_public_channel, + null, + false); + binding.account.setOnItemSelectedListener( + new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected( + AdapterView parent, View view, int position, long id) { + updateJidSuggestion(binding); + } + + @Override + public void onNothingSelected(AdapterView parent) {} + }); + binding.jid.addTextChangedListener( + new TextWatcher() { + @Override + public void beforeTextChanged( + CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + + @Override + public void afterTextChanged(Editable s) { + if (skipTetxWatcher) { + return; + } + if (jidWasModified) { + jidWasModified = !TextUtils.isEmpty(s); + } else { + jidWasModified = !s.toString().equals(getJidSuggestion(binding)); + } + } + }); + updateInputs(binding, false); ArrayList 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 hosts = ((XmppActivity) activity).xmppConnectionService.getKnownConferenceHosts(); + Collection hosts = + ((XmppActivity) activity).xmppConnectionService.getKnownConferenceHosts(); this.knownHostsAdapter.refresh(hosts); } } @@ -271,8 +287,8 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke try { mListener = (CreatePublicChannelDialogListener) context; } catch (ClassCastException e) { - throw new ClassCastException(context.toString() - + " must implement CreateConferenceDialogListener"); + throw new ClassCastException( + context.toString() + " must implement CreateConferenceDialogListener"); } } @@ -280,7 +296,8 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke public void onStart() { super.onStart(); final Activity activity = getActivity(); - if (activity instanceof XmppActivity && ((XmppActivity) activity).xmppConnectionService != null) { + if (activity instanceof XmppActivity + && ((XmppActivity) activity).xmppConnectionService != null) { refreshKnownHosts(); } } diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index 25149a7979d56d77192d05fa48564154275d9aa8..e4d529c71d6e45ee4799ea101f954ddc5fe54325 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/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()); diff --git a/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java b/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java index 873147880cadaa4e1b965ac300699086b2fa87fd..7b8cbc26b52d400ffb24d80833c9fb1d6ebf2c1d 100644 --- a/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java +++ b/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 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; diff --git a/src/main/java/eu/siacs/conversations/ui/MediaBrowserActivity.java b/src/main/java/eu/siacs/conversations/ui/MediaBrowserActivity.java index ac5b07e77dcb543a5a511de2b81c3605834dd280..58d46fd76712840aa5d3537423e5aa5259ea4a2a 100644 --- a/src/main/java/eu/siacs/conversations/ui/MediaBrowserActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/MediaBrowserActivity.java @@ -3,11 +3,7 @@ package eu.siacs.conversations.ui; import android.content.Context; import android.content.Intent; import android.os.Bundle; - import androidx.databinding.DataBindingUtil; - -import java.util.List; - import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityMediaBrowserBinding; import eu.siacs.conversations.entities.Account; @@ -18,6 +14,7 @@ import eu.siacs.conversations.ui.interfaces.OnMediaLoaded; import eu.siacs.conversations.ui.util.Attachment; import eu.siacs.conversations.ui.util.GridManager; import eu.siacs.conversations.xmpp.Jid; +import java.util.List; public class MediaBrowserActivity extends XmppActivity implements OnMediaLoaded { @@ -28,20 +25,17 @@ public class MediaBrowserActivity extends XmppActivity implements OnMediaLoaded @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - this.binding = DataBindingUtil.setContentView(this,R.layout.activity_media_browser); + this.binding = DataBindingUtil.setContentView(this, R.layout.activity_media_browser); Activities.setStatusAndNavigationBarColors(this, binding.getRoot()); setSupportActionBar(binding.toolbar); configureActionBar(getSupportActionBar()); mMediaAdapter = new MediaAdapter(this, R.dimen.media_size); this.binding.media.setAdapter(mMediaAdapter); GridManager.setupLayoutManager(this, this.binding.media, R.dimen.browser_media_size); - } @Override - protected void refreshUiReal() { - - } + protected void refreshUiReal() {} @Override protected void onBackendConnected() { @@ -49,29 +43,30 @@ public class MediaBrowserActivity extends XmppActivity implements OnMediaLoaded String account = intent == null ? null : intent.getStringExtra("account"); String jid = intent == null ? null : intent.getStringExtra("jid"); if (account != null && jid != null) { - xmppConnectionService.getAttachments(account, Jid.ofEscaped(jid), 0, this); + xmppConnectionService.getAttachments(account, Jid.of(jid), 0, this); } } public static void launch(Context context, Contact contact) { - launch(context, contact.getAccount(), contact.getJid().asBareJid().toEscapedString()); + launch(context, contact.getAccount(), contact.getJid().asBareJid().toString()); } public static void launch(Context context, Conversation conversation) { - launch(context, conversation.getAccount(), conversation.getJid().asBareJid().toEscapedString()); + launch(context, conversation.getAccount(), conversation.getJid().asBareJid().toString()); } private static void launch(Context context, Account account, String jid) { final Intent intent = new Intent(context, MediaBrowserActivity.class); - intent.putExtra("account",account.getUuid()); - intent.putExtra("jid",jid); + intent.putExtra("account", account.getUuid()); + intent.putExtra("jid", jid); context.startActivity(intent); } @Override public void onMediaLoaded(List attachments) { - runOnUiThread(()->{ - mMediaAdapter.setAttachments(attachments); - }); + runOnUiThread( + () -> { + mMediaAdapter.setAttachments(attachments); + }); } } diff --git a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java index 7677e1e7510f5546302af6c96791015ab75fe206..1c3a33ab2ff11890efc868ab4e0e52463f423a81 100644 --- a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java +++ b/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); } diff --git a/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java b/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java index 8795c92d95040f4a9dcaab050bd8ea8949bbc85a..b1656cb5bb5b870942e6c3c3f8e51b0806ef9262 100644 --- a/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java +++ b/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 AAC_SENSITIVE_DEVICES = new ImmutableSet.Builder() - .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); } } } diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index 80b4537c16edb89fdcf1a201414695347934f8e8..f0545f8ed567d025a01bba4d37c3458cce06a0c4 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -1,7 +1,6 @@ package eu.siacs.conversations.ui; import static eu.siacs.conversations.utils.PermissionUtils.getFirstDenied; - import static java.util.Arrays.asList; import android.Manifest; @@ -25,13 +24,11 @@ import android.view.MenuItem; import android.view.View; import android.view.WindowManager; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.StringRes; import androidx.databinding.DataBindingUtil; - import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.common.base.Optional; import com.google.common.base.Preconditions; @@ -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 = 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 = 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) { final Intent intent = new Intent(Intent.ACTION_VIEW); - intent.putExtra(EXTRA_ACCOUNT, account.getJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().toString()); if (RtpCapability.jmiSupport(account.getRoster().getContact(with))) { - intent.putExtra(EXTRA_WITH, with.asBareJid().toEscapedString()); + intent.putExtra(EXTRA_WITH, with.asBareJid().toString()); } else { - intent.putExtra(EXTRA_WITH, with.toEscapedString()); + intent.putExtra(EXTRA_WITH, with.toString()); } intent.putExtra(EXTRA_LAST_REPORTED_STATE, state.toString()); intent.putExtra( diff --git a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java index ddc6e253ccbb71717786c2d91f77f2d4c249e98f..02c9b3b25bb47a4892df187b54321d93a3b77e12 100644 --- a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java +++ b/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; } diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index b2329093c26418588c9b7936c925fd360e95757a..27053beca7af66904670eeee48d90e2ec438ff06 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/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 tags = new ArrayList<>(); final var accounts = new ArrayList(); 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)); diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java index 689030c796ab07c98aa5f8fef767f45a4e07da2e..c1a5ef08c9649c23acd2e35b301783e2675e4924 100644 --- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java @@ -1,6 +1,5 @@ package eu.siacs.conversations.ui; -import android.app.AlertDialog; import android.content.Intent; import android.os.Bundle; import android.util.Log; @@ -10,12 +9,9 @@ import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.Toast; - import androidx.appcompat.app.ActionBar; import androidx.databinding.DataBindingUtil; - import com.google.android.material.dialog.MaterialAlertDialogBuilder; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.OmemoSetting; @@ -32,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 ownKeysToTrust = new HashMap<>(); - private final Map> foreignKeysToTrust = new HashMap<>(); - private final OnClickListener mCancelButtonListener = v -> { - setResult(RESULT_CANCELED); - finish(); - }; - private List 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> 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 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 contactJids) { - for (Jid jid : contactJids) { - if (!mAccount.getRoster().getContact(jid).mutualPresenceSubscription()) { - return true; - } - } - return false; - } - - private boolean foreignActuallyHasKeys() { - synchronized (this.foreignKeysToTrust) { - for (Map.Entry> entry : foreignKeysToTrust.entrySet()) { - if (!entry.getValue().isEmpty()) { - return true; - } - } - } - return false; - } - - private boolean reloadFingerprints() { - List acceptedTargets = mConversation == null ? new ArrayList<>() : mConversation.getAcceptedCryptoTargets(); - ownKeysToTrust.clear(); - if (this.mAccount == null) { - return false; - } - AxolotlService service = this.mAccount.getAxolotlService(); - Set 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 foreignKeysSet = service.getKeysWithTrust(FingerprintStatus.createActiveUndecided(), jid); - if (hasNoOtherTrustedKeys(jid) && ownKeysSet.isEmpty()) { - foreignKeysSet.addAll(service.getKeysWithTrust(FingerprintStatus.createActive(false), jid)); - } - Map 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 acceptedTargets = mConversation == null ? new ArrayList<>() : mConversation.getAcceptedCryptoTargets(); - synchronized (this.foreignKeysToTrust) { - for (Map.Entry> entry : foreignKeysToTrust.entrySet()) { - Jid jid = entry.getKey(); - Map 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 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 ownKeysToTrust = new HashMap<>(); + private final Map> foreignKeysToTrust = new HashMap<>(); + private final OnClickListener mCancelButtonListener = + v -> { + setResult(RESULT_CANCELED); + finish(); + }; + private List 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> 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 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 contactJids) { + for (Jid jid : contactJids) { + if (!mAccount.getRoster().getContact(jid).mutualPresenceSubscription()) { + return true; + } + } + return false; + } + + private boolean foreignActuallyHasKeys() { + synchronized (this.foreignKeysToTrust) { + for (Map.Entry> entry : foreignKeysToTrust.entrySet()) { + if (!entry.getValue().isEmpty()) { + return true; + } + } + } + return false; + } + + private boolean reloadFingerprints() { + List acceptedTargets = + mConversation == null + ? new ArrayList<>() + : mConversation.getAcceptedCryptoTargets(); + ownKeysToTrust.clear(); + if (this.mAccount == null) { + return false; + } + AxolotlService service = this.mAccount.getAxolotlService(); + Set 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 foreignKeysSet = + service.getKeysWithTrust(FingerprintStatus.createActiveUndecided(), jid); + if (hasNoOtherTrustedKeys(jid) && ownKeysSet.isEmpty()) { + foreignKeysSet.addAll( + service.getKeysWithTrust(FingerprintStatus.createActive(false), jid)); + } + Map 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 acceptedTargets = + mConversation == null + ? new ArrayList<>() + : mConversation.getAcceptedCryptoTargets(); + synchronized (this.foreignKeysToTrust) { + for (Map.Entry> entry : foreignKeysToTrust.entrySet()) { + Jid jid = entry.getKey(); + Map 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 fingerprints = foreignKeysToTrust.get(jid); + if (hasNoOtherTrustedKeys(jid) + && (fingerprints == null || !fingerprints.containsValue(true))) { + lock(); + return; + } + } + } + unlock(); + } + + private void setDone() { + binding.saveButton.setText(getString(R.string.done)); + } + + private void setFetching() { + binding.saveButton.setText(getString(R.string.fetching_keys)); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java b/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java index fbe42e3b33928fc43847ac5a54ed7107b776415c..c2878041b1453d356d44818803e5876265c58af5 100644 --- a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java +++ b/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() { diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index bee7d2f8ca4f65126214de2d06f58797eafbd664..f86a45c8252ee1a2a566430c05da1795dc8125ea 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/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 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; } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java index f913bd86b6efa484de74b9bf2063a0e60bb6e61d..ac31e248f0c987fe7e4a7d561de80480594bae43 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java +++ b/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 { 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 { } 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 { } } - - public interface OnTglAccountState { void onClickTglAccountState(Account account, boolean state); } - } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java index b085cb964430dcb3c2a6b86231dbc02a7d3e096a..88f7636bfa612fecfe44661104fd717af37896e6 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java @@ -3,14 +3,10 @@ package eu.siacs.conversations.ui.adapter; import android.content.Context; import android.widget.ArrayAdapter; import android.widget.Filter; - import androidx.annotation.NonNull; - import com.google.common.collect.ImmutableList; import com.google.common.collect.Ordering; - import eu.siacs.conversations.Config; - import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -22,61 +18,66 @@ public class KnownHostsAdapter extends ArrayAdapter { private static final Pattern E164_PATTERN = Pattern.compile("^\\+[1-9]\\d{1,14}$"); private List domains; - private final Filter domainFilter = new Filter() { + private final Filter domainFilter = + new Filter() { - @Override - protected FilterResults performFiltering(final CharSequence constraint) { - final ImmutableList.Builder 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 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 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 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 knownHosts) { + public KnownHostsAdapter( + final Context context, final int viewResourceId, final Collection knownHosts) { super(context, viewResourceId, new ArrayList<>()); - domains = Ordering.natural().sortedCopy(knownHosts); + domains = Ordering.natural().sortedCopy(knownHosts); } public KnownHostsAdapter(final Context context, final int viewResourceId) { diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java index 79cf215b56f85744a4ed918f1551bb607dbd83e3..e88ba5d3da16de7d4ef65334135d531295d39278 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java +++ b/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 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 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 ARCHIVE_MIMES = Arrays.asList( @@ -112,6 +124,10 @@ public class MediaAdapter extends RecyclerView.Adapter { - 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 accounts = ImmutableList.copyOf( Lists.transform( requireService().getAccounts(), - a -> a.getJid().asBareJid().toEscapedString())); + a -> a.getJid().asBareJid().toString())); final ImmutableList.Builder entries = new ImmutableList.Builder<>(); final ImmutableList.Builder entryValues = new ImmutableList.Builder<>(); entries.add(getString(R.string.no_account_deactivated)); diff --git a/src/main/java/eu/siacs/conversations/ui/service/AudioPlayer.java b/src/main/java/eu/siacs/conversations/ui/service/AudioPlayer.java index 95f4a54eeeebf325fac4776e1e3f35fb7ba1e11f..2be7ccfba01f80206146caf6b94c0436dde92c72 100644 --- a/src/main/java/eu/siacs/conversations/ui/service/AudioPlayer.java +++ b/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 audioPlayerLayouts = new WeakReferenceSet<>(); private final SensorManager sensorManager; private final Sensor proximitySensor; - private final PendingItem> pendingOnClickView = new PendingItem<>(); + private final PendingItem> 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 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) { diff --git a/src/main/java/eu/siacs/conversations/utils/AccountUtils.java b/src/main/java/eu/siacs/conversations/utils/AccountUtils.java index 4b2c2957f62aaa1e1a2e6e814312a4bb7b136843..bcfffe8c18b294cd03725e98452c584e85e1d45c 100644 --- a/src/main/java/eu/siacs/conversations/utils/AccountUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/AccountUtils.java @@ -5,20 +5,16 @@ import android.content.Intent; import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; - import com.google.common.primitives.Bytes; import com.google.common.primitives.Longs; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.XmppActivity; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; public class AccountUtils { @@ -50,9 +46,7 @@ public class AccountUtils { public static UUID createUuid4(long mostSigBits, long leastSigBits) { final byte[] bytes = - Bytes.concat( - Longs.toByteArray(mostSigBits), - Longs.toByteArray(leastSigBits)); + Bytes.concat(Longs.toByteArray(mostSigBits), Longs.toByteArray(leastSigBits)); bytes[6] &= 0x0f; /* clear version */ bytes[6] |= 0x40; /* set to version 4 */ bytes[8] &= 0x3f; /* clear variant */ @@ -65,7 +59,7 @@ public class AccountUtils { final ArrayList accounts = new ArrayList<>(); for (final Account account : service.getAccounts()) { if (account.isEnabled()) { - accounts.add(account.getJid().asBareJid().toEscapedString()); + accounts.add(account.getJid().asBareJid().toString()); } } return accounts; diff --git a/src/main/java/eu/siacs/conversations/utils/BackupFileHeader.java b/src/main/java/eu/siacs/conversations/utils/BackupFileHeader.java index 404140084136a7c265d1cdb045e639234314ce55..c134a470e4d5116db3ed5629ff77419d7538abd7 100644 --- a/src/main/java/eu/siacs/conversations/utils/BackupFileHeader.java +++ b/src/main/java/eu/siacs/conversations/utils/BackupFileHeader.java @@ -1,13 +1,11 @@ package eu.siacs.conversations.utils; import androidx.annotation.NonNull; - +import eu.siacs.conversations.xmpp.Jid; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import eu.siacs.conversations.xmpp.Jid; - public class BackupFileHeader { private static final int VERSION = 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 {} } diff --git a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java index 2a02d0ddfd77ada76b615f29f7c4b61cd7e4579c..91376ba0efccb2f74a92b4404c71d5ab8c0e48ee 100644 --- a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java +++ b/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 cipherSuites = new LinkedHashSet<>(Arrays.asList(Config.ENABLED_CIPHERS)); + final Collection cipherSuites = + new LinkedHashSet<>(Arrays.asList(Config.ENABLED_CIPHERS)); final List platformCiphers = Arrays.asList(platformSupportedCipherSuites); cipherSuites.retainAll(platformCiphers); cipherSuites.addAll(platformCiphers); @@ -177,7 +187,10 @@ public final class CryptoHelper { } } - public static Pair extractJidAndName(X509Certificate certificate) throws CertificateEncodingException, IllegalArgumentException, CertificateParsingException { + public static Pair extractJidAndName(X509Certificate certificate) + throws CertificateEncodingException, + IllegalArgumentException, + CertificateParsingException { Collection> alternativeNames = certificate.getSubjectAlternativeNames(); List 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 { diff --git a/src/main/java/eu/siacs/conversations/utils/IrregularUnicodeDetector.java b/src/main/java/eu/siacs/conversations/utils/IrregularUnicodeDetector.java index 970ae046d99da128c23da424c9b3f1278d7f5b17..9dcdbb15e11cfb90154392a20409d814d61de376 100644 --- a/src/main/java/eu/siacs/conversations/utils/IrregularUnicodeDetector.java +++ b/src/main/java/eu/siacs/conversations/utils/IrregularUnicodeDetector.java @@ -37,11 +37,9 @@ import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.style.ForegroundColorSpan; import android.util.LruCache; - import androidx.annotation.ColorInt; - import com.google.android.material.color.MaterialColors; - +import eu.siacs.conversations.xmpp.Jid; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -54,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 NORMALIZATION_MAP; - private static final LruCache CACHE = new LruCache<>(4096); - private static final List AMBIGUOUS_CYRILLIC = Arrays.asList("а","г","е","ѕ","і","ј","ķ","ԛ","о","р","с","у","х"); - - static { - Map temp = new HashMap<>(); - temp.put(Character.UnicodeBlock.LATIN_1_SUPPLEMENT, Character.UnicodeBlock.BASIC_LATIN); - NORMALIZATION_MAP = Collections.unmodifiableMap(temp); - } + private static final Map NORMALIZATION_MAP; + private static final LruCache CACHE = new LruCache<>(4096); + private static final List 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 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> mapCompat(String word) { - Map> 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 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> map(String word) { - Map> 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 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> mapCompat(String word) { + Map> 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 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 eliminateFirstAndGetCodePointsCompat(Map> map) { - return eliminateFirstAndGetCodePoints(map, Character.UnicodeBlock.BASIC_LATIN); - } + @TargetApi(Build.VERSION_CODES.N) + private static Map> map(String word) { + Map> 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 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 eliminateFirstAndGetCodePoints(Map> map) { - return eliminateFirstAndGetCodePoints(map, Character.UnicodeScript.COMMON); - } + private static Set eliminateFirstAndGetCodePointsCompat( + Map> map) { + return eliminateFirstAndGetCodePoints(map, Character.UnicodeBlock.BASIC_LATIN); + } - private static Set eliminateFirstAndGetCodePoints(Map> map, T defaultPick) { - T pick = defaultPick; - int size = 0; - for (Map.Entry> entry : map.entrySet()) { - if (entry.getValue().size() > size) { - size = entry.getValue().size(); - pick = entry.getKey(); - } - } - map.remove(pick); - Set all = new HashSet<>(); - for (List codePoints : map.values()) { - all.addAll(codePoints); - } - return all; - } + @TargetApi(Build.VERSION_CODES.N) + private static Set eliminateFirstAndGetCodePoints( + Map> map) { + return eliminateFirstAndGetCodePoints(map, Character.UnicodeScript.COMMON); + } - private static Set findIrregularCodePoints(String word) { - Set codePoints; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - final Map> map = mapCompat(word); - final Set set = asSet(map); - if (containsOnlyAmbiguousCyrillic(set)) { - return set; - } - codePoints = eliminateFirstAndGetCodePointsCompat(map); - } else { - final Map> map = map(word); - final Set set = asSet(map); - if (containsOnlyAmbiguousCyrillic(set)) { - return set; - } - codePoints = eliminateFirstAndGetCodePoints(map); - } - return codePoints; - } + private static Set eliminateFirstAndGetCodePoints( + Map> map, T defaultPick) { + T pick = defaultPick; + int size = 0; + for (Map.Entry> entry : map.entrySet()) { + if (entry.getValue().size() > size) { + size = entry.getValue().size(); + pick = entry.getKey(); + } + } + map.remove(pick); + Set all = new HashSet<>(); + for (List codePoints : map.values()) { + all.addAll(codePoints); + } + return all; + } - private static Set asSet(Map> map) { - final Set flat = new HashSet<>(); - for(List value : map.values()) { - flat.addAll(value); - } - return flat; - } + private static Set findIrregularCodePoints(String word) { + Set codePoints; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + final Map> map = mapCompat(word); + final Set set = asSet(map); + if (containsOnlyAmbiguousCyrillic(set)) { + return set; + } + codePoints = eliminateFirstAndGetCodePointsCompat(map); + } else { + final Map> map = map(word); + final Set set = asSet(map); + if (containsOnlyAmbiguousCyrillic(set)) { + return set; + } + codePoints = eliminateFirstAndGetCodePoints(map); + } + return codePoints; + } + private static Set asSet(Map> map) { + final Set flat = new HashSet<>(); + for (List value : map.values()) { + flat.addAll(value); + } + return flat; + } - private static boolean containsOnlyAmbiguousCyrillic(Collection codePoints) { - for (String codePoint : codePoints) { - if (!AMBIGUOUS_CYRILLIC.contains(codePoint)) { - return false; - } - } - return true; - } + private static boolean containsOnlyAmbiguousCyrillic(Collection 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 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 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 domain; + private static class PatternTuple { + private final Pattern local; + private final List domain; - private PatternTuple(Pattern local, List domain) { - this.local = local; - this.domain = domain; - } + private PatternTuple(Pattern local, List 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 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 domainPatterns = new ArrayList<>(); + if (domain != null) { + for (String label : domain.split("\\.")) { + domainPatterns.add(create(findIrregularCodePoints(label))); + } + } + return new PatternTuple(localPattern, domainPatterns); + } + } } diff --git a/src/main/java/eu/siacs/conversations/utils/JidHelper.java b/src/main/java/eu/siacs/conversations/utils/JidHelper.java index e7443c8bb08bae1851d09f02e331d8b09763dc40..aac7d156b1c105702205ba47b30f32718d529903 100644 --- a/src/main/java/eu/siacs/conversations/utils/JidHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/JidHelper.java @@ -29,22 +29,19 @@ package eu.siacs.conversations.utils; - +import eu.siacs.conversations.Config; +import eu.siacs.conversations.xmpp.Jid; import java.util.Arrays; import java.util.List; import java.util.Locale; -import eu.siacs.conversations.Config; -import eu.siacs.conversations.xmpp.InvalidJid; -import eu.siacs.conversations.xmpp.Jid; - public class JidHelper { private static final List LOCAL_PART_BLACKLIST = Arrays.asList("xmpp", "jabber", "me"); public static String localPartOrFallback(Jid jid) { if (LOCAL_PART_BLACKLIST.contains(jid.getLocal().toLowerCase(Locale.ENGLISH))) { - final String domain = jid.getDomain().toEscapedString(); + final String domain = jid.getDomain().toString(); final int index = domain.indexOf('.'); return index > 1 ? domain.substring(0, index) : domain; } else { @@ -52,16 +49,7 @@ public class JidHelper { } } - public static Jid parseOrFallbackToInvalid(String jid) { - try { - return Jid.of(jid); - } catch (IllegalArgumentException e) { - return InvalidJid.of(jid, true); - } - } - public static boolean isQuicksyDomain(final Jid jid) { return Config.QUICKSY_DOMAIN != null && Config.QUICKSY_DOMAIN.equals(jid.getDomain()); } - } diff --git a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java index 1df2f8a2407a519f22da70c0d2675796aaa1ad6f..d1d2140c0bbe87e0174cd79599973cb49529ce2b 100644 --- a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java +++ b/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 AMBIGUOUS_CONTAINER_FORMATS = ImmutableList.of( - "application/ogg", - "video/3gpp", // .3gp files can contain audio, video or both - "video/3gpp2" - ); + public static final List AMBIGUOUS_CONTAINER_FORMATS = + ImmutableList.of( + "application/ogg", + "video/3gpp", // .3gp files can contain audio, video or both + "video/3gpp2"); private static final Map mimeTypeToExtensionMap = new HashMap<>(); private static final Map 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 PATH_PRECEDENCE_MIME_TYPE = Arrays.asList("audio/x-m4b"); + private static final Collection 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; } diff --git a/src/main/java/eu/siacs/conversations/utils/XmppUri.java b/src/main/java/eu/siacs/conversations/utils/XmppUri.java index ecd809010f428f34fff65854366f8a7cf8de7069..6a39a6aacb668b625ce8689ae50c63e21b1ad9e1 100644 --- a/src/main/java/eu/siacs/conversations/utils/XmppUri.java +++ b/src/main/java/eu/siacs/conversations/utils/XmppUri.java @@ -1,14 +1,12 @@ package eu.siacs.conversations.utils; import android.net.Uri; - import androidx.annotation.NonNull; - import com.google.common.base.CharMatcher; import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; - +import eu.siacs.conversations.xmpp.Jid; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.ArrayList; @@ -18,8 +16,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import eu.siacs.conversations.xmpp.Jid; - public class XmppUri { public static final String ACTION_JOIN = "join"; @@ -42,8 +38,8 @@ public class XmppUri { parse(Uri.parse(uri)); } catch (IllegalArgumentException e) { try { - jid = Jid.ofEscaped(uri).asBareJid().toEscapedString(); - } catch (IllegalArgumentException e2) { + jid = Jid.of(uri).asBareJid().toString(); + } catch (final IllegalArgumentException e2) { jid = null; } } @@ -60,7 +56,8 @@ public class XmppUri { private static Map parseParameters(final String query, final char seperator) { final ImmutableMap.Builder 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 fingerprints, char separator) { + public static String getFingerprintUri( + final String base, final List 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 { diff --git a/src/main/java/eu/siacs/conversations/worker/ExportBackupWorker.java b/src/main/java/eu/siacs/conversations/worker/ExportBackupWorker.java index 9e24282d1a46d49fa1dde042d3cb4b3df684aef4..7564fb451a7fe89849ba52eb0b02edf5783738c2 100644 --- a/src/main/java/eu/siacs/conversations/worker/ExportBackupWorker.java +++ b/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 { diff --git a/src/main/java/eu/siacs/conversations/xml/Element.java b/src/main/java/eu/siacs/conversations/xml/Element.java index 3371bdf081c785450e90bebef4edcc3340865545..8bfef2e91f0de7c5650013c2f7a2febfbbfbe2d3 100644 --- a/src/main/java/eu/siacs/conversations/xml/Element.java +++ b/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; } diff --git a/src/main/java/eu/siacs/conversations/xmpp/InvalidJid.java b/src/main/java/eu/siacs/conversations/xmpp/InvalidJid.java deleted file mode 100644 index 4e3092821d4c241a004e5ce77f7e8e2940dea0ea..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/InvalidJid.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (c) 2018, Daniel Gultsch All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors - * may be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package eu.siacs.conversations.xmpp; - -import androidx.annotation.NonNull; - -import im.conversations.android.xmpp.model.stanza.Stanza; - -public class InvalidJid implements Jid { - - private final String value; - - private InvalidJid(String jid) { - this.value = jid; - } - - public static Jid of(String jid, boolean fallback) { - final int pos = jid.indexOf('/'); - if (fallback && pos >= 0 && jid.length() >= pos + 1) { - if (jid.substring(pos+1).trim().isEmpty()) { - return Jid.ofEscaped(jid.substring(0,pos)); - } - } - return new InvalidJid(jid); - } - - @Override - @NonNull - public String toString() { - return value; - } - - @Override - public boolean isFullJid() { - throw new AssertionError("Not implemented"); - } - - @Override - public boolean isBareJid() { - throw new AssertionError("Not implemented"); - } - - @Override - public boolean isDomainJid() { - throw new AssertionError("Not implemented"); - } - - @Override - public Jid asBareJid() { - throw new AssertionError("Not implemented"); - } - - - @Override - public Jid withResource(CharSequence charSequence) { - throw new AssertionError("Not implemented"); - } - - @Override - public String getLocal() { - throw new AssertionError("Not implemented"); - } - - @Override - public String getEscapedLocal() { - throw new AssertionError("Not implemented"); - } - - @Override - public Jid getDomain() { - throw new AssertionError("Not implemented"); - } - - @Override - public String getResource() { - throw new AssertionError("Not implemented"); - } - - @Override - public String toEscapedString() { - throw new AssertionError("Not implemented"); - } - - @Override - public int length() { - return value.length(); - } - - @Override - public char charAt(int index) { - return value.charAt(index); - } - - @Override - public CharSequence subSequence(int start, int end) { - return value.subSequence(start, end); - } - - @Override - public int compareTo(@NonNull Jid o) { - throw new AssertionError("Not implemented"); - } - - public static Jid getNullForInvalid(Jid jid) { - if (jid instanceof InvalidJid) { - return null; - } else { - return jid; - } - } - - public static boolean isValid(Jid jid) { - return !(jid instanceof InvalidJid); - } - - public static boolean hasValidFrom(Stanza stanza) { - final String from = stanza.getAttribute("from"); - if (from == null) { - return false; - } - try { - Jid.ofEscaped(from); - return true; - } catch (IllegalArgumentException e) { - return false; - } - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/Jid.java b/src/main/java/eu/siacs/conversations/xmpp/Jid.java index 299c872b3c665bdcf20aa06fc5be1107a7a46999..c6b26903f32bea1deb59182cda9938112bf4bbc2 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/Jid.java +++ b/src/main/java/eu/siacs/conversations/xmpp/Jid.java @@ -1,20 +1,19 @@ package eu.siacs.conversations.xmpp; +import androidx.annotation.NonNull; +import com.google.common.base.CharMatcher; +import im.conversations.android.xmpp.model.stanza.Stanza; +import java.io.Serializable; import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.jid.parts.Domainpart; import org.jxmpp.jid.parts.Localpart; import org.jxmpp.jid.parts.Resourcepart; import org.jxmpp.stringprep.XmppStringprepException; -import java.io.Serializable; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public interface Jid extends Comparable, Serializable, CharSequence { - - Pattern JID = Pattern.compile("^((.*?)@)?([^/@]+)(/(.*))?$"); +public abstract class Jid implements Comparable, 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, Serializable, CharSequence { return ofLocalAndDomain(local, domain); } try { - return new WrappedJid(JidCreate.entityFullFrom( - Localpart.fromUnescaped(local.toString()), - Domainpart.from(domain.toString()), - Resourcepart.from(resource.toString()) - )); - } catch (XmppStringprepException e) { + return new InternalRepresentation( + JidCreate.entityFullFrom( + Localpart.from(local.toString()), + Domainpart.from(domain.toString()), + Resourcepart.from(resource.toString()))); + } catch (final XmppStringprepException e) { throw new IllegalArgumentException(e); } } - static Jid ofEscaped(CharSequence local, CharSequence domain, CharSequence resource) { + public static Jid ofDomain(final CharSequence domain) { try { - if (resource == null) { - return new WrappedJid( - JidCreate.bareFrom( - Localpart.from(local.toString()), - Domainpart.from(domain.toString()) - ) - ); - } - return new WrappedJid(JidCreate.entityFullFrom( - Localpart.from(local.toString()), - Domainpart.from(domain.toString()), - Resourcepart.from(resource.toString()) - )); - } catch (XmppStringprepException e) { - throw new IllegalArgumentException(e); - } - } - - - static Jid ofDomain(CharSequence domain) { - try { - return new WrappedJid(JidCreate.domainBareFrom(domain)); - } catch (XmppStringprepException e) { + return new InternalRepresentation(JidCreate.domainBareFrom(domain)); + } catch (final XmppStringprepException e) { throw new IllegalArgumentException(e); } } - static Jid ofLocalAndDomain(CharSequence local, CharSequence domain) { + public static Jid ofLocalAndDomain(final CharSequence local, final CharSequence domain) { try { - return new WrappedJid( + return new InternalRepresentation( JidCreate.bareFrom( - Localpart.fromUnescaped(local.toString()), - Domainpart.from(domain.toString()) - ) - ); - } catch (XmppStringprepException e) { + Localpart.from(local.toString()), Domainpart.from(domain.toString()))); + } catch (final XmppStringprepException e) { throw new IllegalArgumentException(e); } } - static Jid ofDomainAndResource(CharSequence domain, CharSequence resource) { + public static Jid ofDomainAndResource(CharSequence domain, CharSequence resource) { try { - return new WrappedJid( + return new InternalRepresentation( JidCreate.domainFullFrom( Domainpart.from(domain.toString()), - Resourcepart.from(resource.toString()) - )); - } catch (XmppStringprepException e) { + Resourcepart.from(resource.toString()))); + } catch (final XmppStringprepException e) { throw new IllegalArgumentException(e); } } - static Jid ofLocalAndDomainEscaped(CharSequence local, CharSequence domain) { + public static Jid of(final CharSequence input) { + if (input instanceof Jid jid) { + return jid; + } try { - return new WrappedJid( - JidCreate.bareFrom( - Localpart.from(local.toString()), - Domainpart.from(domain.toString()) - ) - ); - } catch (XmppStringprepException e) { + return new InternalRepresentation(JidCreate.from(input)); + } catch (final XmppStringprepException e) { throw new IllegalArgumentException(e); } } - static Jid of(CharSequence jid) { - if (jid instanceof Jid) { - return (Jid) jid; - } - Matcher matcher = JID.matcher(jid); - if (matcher.matches()) { - return of(matcher.group(2), matcher.group(3), matcher.group(5)); - } else { - throw new IllegalArgumentException("Could not parse JID: " + jid); + public static Jid ofUserInput(final CharSequence input) { + final var jid = of(input); + if (CharMatcher.is('@').matchesAnyOf(jid.getDomain())) { + throw new IllegalArgumentException("Domain should not contain @"); } + return jid; + } + + public static Jid ofOrInvalid(final String input) { + return ofOrInvalid(input, false); } - static Jid ofEscaped(CharSequence jid) { + /** + * @param jid a string representation of the jid to parse + * @param fallback indicates whether an attempt should be made to parse a bare version of the + * jid + * @return an instance of Jid; may be Jid.Invalid + */ + public static Jid ofOrInvalid(final String jid, final boolean fallback) { try { - return new WrappedJid(JidCreate.from(jid)); - } catch (final XmppStringprepException e) { - throw new IllegalArgumentException(e); + return Jid.of(jid); + } catch (final IllegalArgumentException e) { + return Jid.invalidOf(jid, fallback); } } - boolean isFullJid(); + private static Jid invalidOf(final String jid, boolean fallback) { + final int pos = jid.indexOf('/'); + if (fallback && pos >= 0 && jid.length() >= pos + 1) { + if (jid.substring(pos + 1).trim().isEmpty()) { + return Jid.of(jid.substring(0, pos)); + } + } + return new Invalid(jid); + } + + public abstract boolean isFullJid(); + + public abstract boolean isBareJid(); + + public abstract boolean isDomainJid(); + + public abstract Jid asBareJid(); - boolean isBareJid(); + public abstract Jid withResource(CharSequence resource); - boolean isDomainJid(); + public abstract String getLocal(); - Jid asBareJid(); + public abstract Jid getDomain(); - Jid withResource(CharSequence resource); + public abstract String getResource(); - String getLocal(); + private static class InternalRepresentation extends Jid { + private final org.jxmpp.jid.Jid inner; - String getEscapedLocal(); + private InternalRepresentation(final org.jxmpp.jid.Jid inner) { + this.inner = inner; + } + + @Override + public boolean isFullJid() { + return inner.isEntityFullJid() || inner.isDomainFullJid(); + } - Jid getDomain(); + @Override + public boolean isBareJid() { + return inner.isDomainBareJid() || inner.isEntityBareJid(); + } - String getResource(); + @Override + public boolean isDomainJid() { + return inner.isDomainBareJid() || inner.isDomainFullJid(); + } + + @Override + public Jid asBareJid() { + return new InternalRepresentation(inner.asBareJid()); + } + + @Override + public Jid withResource(CharSequence resource) { + final Localpart localpart = inner.getLocalpartOrNull(); + try { + final Resourcepart resourcepart = Resourcepart.from(resource.toString()); + if (localpart == null) { + return new InternalRepresentation( + JidCreate.domainFullFrom(inner.getDomain(), resourcepart)); + } else { + return new InternalRepresentation( + JidCreate.fullFrom(localpart, inner.getDomain(), resourcepart)); + } + } catch (XmppStringprepException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public String getLocal() { + final Localpart localpart = inner.getLocalpartOrNull(); + return localpart == null ? null : localpart.toString(); + } + + @Override + public Jid getDomain() { + return new InternalRepresentation(inner.asDomainBareJid()); + } + + @Override + public String getResource() { + final Resourcepart resourcepart = inner.getResourceOrNull(); + return resourcepart == null ? null : resourcepart.toString(); + } + + @NonNull + @Override + public String toString() { + return inner.toString(); + } + + @Override + public int length() { + return inner.length(); + } - String toEscapedString(); + @Override + public char charAt(int i) { + return inner.charAt(i); + } + + @NonNull + @Override + public CharSequence subSequence(int i, int i1) { + return inner.subSequence(i, i1); + } + + @Override + public int compareTo(Jid jid) { + if (jid instanceof InternalRepresentation) { + return inner.compareTo(((InternalRepresentation) jid).inner); + } else { + return 0; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + InternalRepresentation that = (InternalRepresentation) o; + return inner.equals(that.inner); + } + + @Override + public int hashCode() { + return inner.hashCode(); + } + } + + public static class Invalid extends Jid { + + private final String value; + + private Invalid(final String jid) { + this.value = jid; + } + + @Override + @NonNull + public String toString() { + return value; + } + + @Override + public boolean isFullJid() { + throw new AssertionError("Not implemented"); + } + + @Override + public boolean isBareJid() { + throw new AssertionError("Not implemented"); + } + + @Override + public boolean isDomainJid() { + throw new AssertionError("Not implemented"); + } + + @Override + public Jid asBareJid() { + throw new AssertionError("Not implemented"); + } + + @Override + public Jid withResource(CharSequence charSequence) { + throw new AssertionError("Not implemented"); + } + + @Override + public String getLocal() { + throw new AssertionError("Not implemented"); + } + + @Override + public Jid getDomain() { + throw new AssertionError("Not implemented"); + } + + @Override + public String getResource() { + throw new AssertionError("Not implemented"); + } + + @Override + public int length() { + return value.length(); + } + + @Override + public char charAt(int index) { + return value.charAt(index); + } + + @NonNull + @Override + public CharSequence subSequence(int start, int end) { + return value.subSequence(start, end); + } + + @Override + public int compareTo(@NonNull Jid o) { + throw new AssertionError("Not implemented"); + } + + public static Jid getNullForInvalid(final Jid jid) { + if (jid instanceof Invalid) { + return null; + } else { + return jid; + } + } + + public static boolean isValid(Jid jid) { + return !(jid instanceof Invalid); + } + + public static boolean hasValidFrom(final Stanza stanza) { + final String from = stanza.getAttribute("from"); + if (from == null) { + return false; + } + try { + Jid.of(from); + return true; + } catch (final IllegalArgumentException e) { + return false; + } + } + } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/WrappedJid.java b/src/main/java/eu/siacs/conversations/xmpp/WrappedJid.java deleted file mode 100644 index 08fb6e6dcc832ea105c160bf6132c91abddd095c..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/WrappedJid.java +++ /dev/null @@ -1,130 +0,0 @@ -package eu.siacs.conversations.xmpp; - - -import androidx.annotation.NonNull; - -import org.jxmpp.jid.Jid; -import org.jxmpp.jid.impl.JidCreate; -import org.jxmpp.jid.parts.Localpart; -import org.jxmpp.jid.parts.Resourcepart; -import org.jxmpp.stringprep.XmppStringprepException; - - -public class WrappedJid implements eu.siacs.conversations.xmpp.Jid { - private final Jid inner; - - WrappedJid(Jid inner) { - this.inner = inner; - } - - @Override - public boolean isFullJid() { - return inner.isEntityFullJid() || inner.isDomainFullJid(); - } - - @Override - public boolean isBareJid() { - return inner.isDomainBareJid() || inner.isEntityBareJid(); - } - - @Override - public boolean isDomainJid() { - return inner.isDomainBareJid() || inner.isDomainFullJid(); - } - - @Override - public eu.siacs.conversations.xmpp.Jid asBareJid() { - return new WrappedJid(inner.asBareJid()); - } - - @Override - public eu.siacs.conversations.xmpp.Jid withResource(CharSequence resource) { - final Localpart localpart = inner.getLocalpartOrNull(); - try { - final Resourcepart resourcepart = Resourcepart.from(resource.toString()); - if (localpart == null) { - return new WrappedJid(JidCreate.domainFullFrom(inner.getDomain(),resourcepart)); - } else { - return new WrappedJid( - JidCreate.fullFrom( - localpart, - inner.getDomain(), - resourcepart - )); - } - } catch (XmppStringprepException e) { - throw new IllegalArgumentException(e); - } - } - - @Override - public String getLocal() { - final Localpart localpart = inner.getLocalpartOrNull(); - return localpart == null ? null : localpart.asUnescapedString(); - } - - @Override - public String getEscapedLocal() { - final Localpart localpart = inner.getLocalpartOrNull(); - return localpart == null ? null : localpart.toString(); - } - - @Override - public eu.siacs.conversations.xmpp.Jid getDomain() { - return new WrappedJid(inner.asDomainBareJid()); - } - - @Override - public String getResource() { - final Resourcepart resourcepart = inner.getResourceOrNull(); - return resourcepart == null ? null : resourcepart.toString(); - } - - @Override - public String toEscapedString() { - return inner.toString(); - } - - @NonNull - @Override - public String toString() { - return inner.asUnescapedString(); - } - - @Override - public int length() { - return inner.length(); - } - - @Override - public char charAt(int i) { - return inner.charAt(i); - } - - @Override - public CharSequence subSequence(int i, int i1) { - return inner.subSequence(i,i1); - } - - @Override - public int compareTo(eu.siacs.conversations.xmpp.Jid jid) { - if (jid instanceof WrappedJid) { - return inner.compareTo(((WrappedJid) jid).inner); - } else { - return 0; - } - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - WrappedJid that = (WrappedJid) o; - return inner.equals(that.inner); - } - - @Override - public int hashCode() { - return inner.hashCode(); - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 0707453ff0df3ca02b5d1bbc27d8a7118433ad0a..6f8cc03465239a06995d74b15978e827dbff1db3 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/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 getMucServersWithholdAccount() { final List servers = getMucServers(); - servers.remove(account.getDomain().toEscapedString()); + servers.remove(account.getDomain().toString()); return servers; } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index 4d4ac2fb274313aae5d72e1657d371a19fb1efaf..b95d330401b0f92342ee3d8bf8598451fb43936d 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -4,9 +4,7 @@ import android.telecom.TelecomManager; import android.telecom.VideoProfile; import android.util.Base64; import android.util.Log; - import androidx.annotation.Nullable; - import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.base.Preconditions; @@ -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 ? } @@ -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()); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 362f68a13810f20912e827273f6966dfccf4f62f..806c5d9b58b0e6e7c557f7901799939c3fa329ca 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/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); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java index 2925592ea498772ccf62ab48d7b183562f21bf17..8cebf080fd14835dde8d5a36002b929a63514f98 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java @@ -1,10 +1,8 @@ package eu.siacs.conversations.xmpp.jingle.transports; import android.util.Log; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.common.base.Joiner; import com.google.common.base.MoreObjects; import com.google.common.base.Optional; @@ -22,7 +20,6 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.SettableFuture; - import eu.siacs.conversations.Config; import eu.siacs.conversations.utils.SocksSocketFactory; import eu.siacs.conversations.xml.Element; @@ -33,7 +30,6 @@ import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection; import eu.siacs.conversations.xmpp.jingle.DirectConnectionUtils; import eu.siacs.conversations.xmpp.jingle.stanzas.SocksByteStreamsTransportInfo; import im.conversations.android.xmpp.model.stanza.Iq; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -104,8 +100,8 @@ public class SocksByteStreamsTransport implements Transport { .join( Arrays.asList( streamId, - id.with.toEscapedString(), - id.account.getJid().toEscapedString())), + id.with.toString(), + id.account.getJid().toString())), StandardCharsets.UTF_8) .toString(); final var ourDestination = @@ -115,8 +111,8 @@ public class SocksByteStreamsTransport implements Transport { .join( Arrays.asList( streamId, - id.account.getJid().toEscapedString(), - id.with.toEscapedString())), + id.account.getJid().toString(), + id.with.toString())), StandardCharsets.UTF_8) .toString(); @@ -255,7 +251,7 @@ public class SocksByteStreamsTransport implements Transport { final Element query = proxyActivation.addChild("query", Namespace.BYTE_STREAMS); query.setAttribute("sid", this.streamId); final Element activate = query.addChild("activate"); - activate.setContent(id.with.toEscapedString()); + activate.setContent(id.with.toString()); xmppConnection.sendIqPacket( proxyActivation, (response) -> { @@ -731,10 +727,12 @@ public class SocksByteStreamsTransport implements Transport { && selectedByThemCandidatePriority > candidate.priority) { Log.d( Config.LOGTAG, - "The candidate selected by peer had a higher priority then anything we could try"); + "The candidate selected by peer had a higher priority then anything we" + + " could try"); connectionFuture.setException( new CandidateErrorException( - "The candidate selected by peer had a higher priority then anything we could try")); + "The candidate selected by peer had a higher priority then" + + " anything we could try")); return; } try { @@ -864,7 +862,7 @@ public class SocksByteStreamsTransport implements Transport { return new Candidate( cid, host, - Jid.ofEscaped(jid), + Jid.of(jid), Integer.parseInt(port), Integer.parseInt(priority), CandidateType.valueOf(type.toUpperCase(Locale.ROOT))); diff --git a/src/main/java/im/conversations/android/xmpp/model/Extension.java b/src/main/java/im/conversations/android/xmpp/model/Extension.java index 0d4c50eef3b4a8626ee6f1eaf2d619978f373f78..ecfe09aa239006e15474099ac4674c1cd4c1516f 100644 --- a/src/main/java/im/conversations/android/xmpp/model/Extension.java +++ b/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 getOnlyExtension(final Class clazz) { + final var extensions = getExtensions(clazz); + if (extensions.size() == 1) { + return Iterables.getOnlyElement(extensions); + } + return null; + } + public Collection getExtensions(final Class clazz) { return Collections2.transform( Collections2.filter(getChildren(), clazz::isInstance), clazz::cast); diff --git a/src/main/java/im/conversations/android/xmpp/model/bind/Bind.java b/src/main/java/im/conversations/android/xmpp/model/bind/Bind.java index 27264f7545e079f2c3cb6415f4763ab3c334dc3b..6a7c8605cc1c8262ec356da6295a5a1d97cb8147 100644 --- a/src/main/java/im/conversations/android/xmpp/model/bind/Bind.java +++ b/src/main/java/im/conversations/android/xmpp/model/bind/Bind.java @@ -1,7 +1,6 @@ package im.conversations.android.xmpp.model.bind; import com.google.common.base.Strings; - import im.conversations.android.annotation.XmlElement; import im.conversations.android.xmpp.model.Extension; @@ -26,7 +25,7 @@ public class Bind extends Extension { return null; } try { - return eu.siacs.conversations.xmpp.Jid.ofEscaped(content); + return eu.siacs.conversations.xmpp.Jid.of(content); } catch (final IllegalArgumentException e) { return null; } diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/AuthorizationIdentifier.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/AuthorizationIdentifier.java index e29ae7dea3ced429d4e25d17f1fe1954017b5d98..d477131b886eb09afdd30a33bcfc46eb095ec04a 100644 --- a/src/main/java/im/conversations/android/xmpp/model/sasl2/AuthorizationIdentifier.java +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/AuthorizationIdentifier.java @@ -1,7 +1,6 @@ package im.conversations.android.xmpp.model.sasl2; import com.google.common.base.Strings; - import eu.siacs.conversations.xmpp.Jid; import im.conversations.android.annotation.XmlElement; import im.conversations.android.xmpp.model.Extension; @@ -9,18 +8,17 @@ import im.conversations.android.xmpp.model.Extension; @XmlElement public class AuthorizationIdentifier extends Extension { - public AuthorizationIdentifier() { super(AuthorizationIdentifier.class); } public Jid get() { final var content = getContent(); - if ( Strings.isNullOrEmpty(content)) { + if (Strings.isNullOrEmpty(content)) { return null; } try { - return Jid.ofEscaped(content); + return Jid.of(content); } catch (final IllegalArgumentException e) { return null; } diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java b/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java index 82a8ce3dfbfa1f225e6030ace9f746cdff09c9cd..3abf016fffc3cfc8b60766c43f2412dbbed7effa 100644 --- a/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java +++ b/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java @@ -1,10 +1,7 @@ package im.conversations.android.xmpp.model.stanza; import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; - -import im.conversations.android.xmpp.model.Extension; import im.conversations.android.xmpp.model.StreamElement; import im.conversations.android.xmpp.model.error.Error; @@ -45,7 +42,7 @@ public abstract class Stanza extends StreamElement { public boolean isInvalid() { final var to = getTo(); final var from = getFrom(); - if (to instanceof InvalidJid || from instanceof InvalidJid) { + if (to instanceof Jid.Invalid || from instanceof Jid.Invalid) { return true; } return false; diff --git a/src/main/res/drawable/ic_description_48dp.xml b/src/main/res/drawable/ic_description_48dp.xml index 6d83bda444728ba9064d77360637ad4d996cc16b..4dbaa7a7b42304fc5eb4d769b662faae24e73833 100644 --- a/src/main/res/drawable/ic_description_48dp.xml +++ b/src/main/res/drawable/ic_description_48dp.xml @@ -1,5 +1,12 @@ - - - - + + + + diff --git a/src/main/res/drawable/ic_slideshow_48dp.xml b/src/main/res/drawable/ic_slideshow_48dp.xml new file mode 100644 index 0000000000000000000000000000000000000000..5e017d3dcd3da38ca2b63e7f74c92a5e96dbd0c2 --- /dev/null +++ b/src/main/res/drawable/ic_slideshow_48dp.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/src/main/res/drawable/ic_table_48dp.xml b/src/main/res/drawable/ic_table_48dp.xml new file mode 100644 index 0000000000000000000000000000000000000000..1d356de74488e18918ab7e08526472cd87292e05 --- /dev/null +++ b/src/main/res/drawable/ic_table_48dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/res/layout/item_message_content.xml b/src/main/res/layout/item_message_content.xml index 903567af899f36cd4d03fd30713b31231553eb2f..861d04bd55af4db7b39b0d4747aa24cf48ee4926 100644 --- a/src/main/res/layout/item_message_content.xml +++ b/src/main/res/layout/item_message_content.xml @@ -1,6 +1,7 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> @@ -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"> - + app:iconSize="26dp" + app:icon="@drawable/ic_play_arrow_24dp" /> Este campo es requerido Corregir mensaje Enviar mensaje corregido - Ya has confiado la huella digital de esta persona. Al seleccionar “Listo” solo estás confirmando que %s es parte de este chat grupal. + 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. Has deshabilitado esta cuenta Error de seguridad: ¡Acceso a archivo inválido! No se ha encontrado ninguna aplicación para compartir la URI @@ -1011,7 +1011,7 @@ Tu contacto utiliza dispositivos no verificados. Escanea su código QR para realizar la verificación e impedir ataques MITM activos. Desconectarse Desconectado - Está utilizando dispositivos no verificados. Escanea el código QR en tus otros dispositivos para realizar la verificación e impedir ataques MITM activos. + 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. Informar de spam y bloquear al spammer Informar sobre spam ¡Bienvenido a Quicksy! @@ -1059,7 +1059,7 @@ Organismos de certificación Confiar en los certificados CA del sistema Requerir enlace al canal - La vinculación de canales puede detectar algunos ataques al intermediario + La vinculación de canales puede detectar algunos ataques de intermediario Conexión al servidor Sistema operativo En el dispositivo @@ -1103,7 +1103,7 @@ La llamada está usando el altavoz. La llamada está usando bluetooth. Video desactivado. Toca para activar. - Apartado de inicio de sesión + Método de acceso No se pudo agregar la reacción Agregar reacción… Agregar reacción diff --git a/src/main/res/values-fi/strings.xml b/src/main/res/values-fi/strings.xml index 41b7d6b578eebe8bd6954fa9f578730329c1c3b9..0a15eecba996ca04abfbb6d0eba8503889eedf71 100644 --- a/src/main/res/values-fi/strings.xml +++ b/src/main/res/values-fi/strings.xml @@ -319,12 +319,12 @@ Varmuuskopiosi on palautettu Älä unohda ottaa tiliä käyttöön. Valitse tiedosto - Vastaanotetaan %1$s (%2$d%% valmis) + Vastaanotetaan %1$s (%2$d% % valmiina) Lataa %s Poista %s tiedosto Avaa %s - Lähetetään (%1$d%% valmis) + lähetetään (%1$d% % valmiina) Valmistellaan tiedoston lähettämistä %s tarjottu ladattavaksi Peru siirto diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index 5c4b130c3e59cb5ae93b258eae0274b49606c53b..5dec1fa5f0fd6a979a557bd0e6ba2343f4e184be 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -454,7 +454,7 @@ ダウンロード失敗: 無効なファイル Tor ネットワークが利用できません バインド失敗 - そのサーバーはこのドメインに責任を持ちません + このドメインに責任を持ちません 壊れています 在席状況 デバイスがロックされているときは離席 @@ -1089,4 +1089,5 @@ ふきだし 背景色、文字サイズ、プロフィール画像など ふきだし + 接続タイムアウト diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml index c017b425c29682e9470bd34dfb7c5387686f1bde..f6d828695d49f710e974b99d30cbf15110b360bf 100644 --- a/src/main/res/values-pt-rBR/strings.xml +++ b/src/main/res/values-pt-rBR/strings.xml @@ -27,7 +27,7 @@ %d minutos atrás %d conversa não lida - %d conversas não lidas + %d de conversas não lidas %d conversas não lidas enviando… @@ -444,7 +444,7 @@ Excluir a seleção %d certificado cancelado - %d certificados cancelados + %d de certificados cancelados %d certificados cancelados Troca o botão \"Enviar\" pelo de ação rápida @@ -502,7 +502,7 @@ %1$d de %2$d contas conectadas %d mensagem - %d mensagens + %d de mensagens %d mensagens Carregar mais mensagens @@ -628,32 +628,32 @@ 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\". %d segundo - %d segundos + %d de segundos %d segundos %d minuto - %d minutos + %d de minutos %d minutos %d hora - %d horas + %d de horas %d horas %d dia - %d dias + %d de dias %d dias %d semana - %d semanas + %d de semanas %d semanas %d mês - %d meses + %d de meses %d meses Exclusão automática de mensagens @@ -928,18 +928,18 @@ Chamada realizada Chamada perdida - %1$d chamada perdida para %2$s - %1$d chamadas perdidas para %2$s - %1$d chamadas perdidas para %2$s + %1$d chamada perdida de %2$s + %1$d de chamadas perdidas de %2$s + %1$d chamadas perdidas de %2$s %d chamada perdida - %d chamadas perdidas + %d de chamadas perdidas %d chamadas perdidas %1$d chamadas perdidas de %2$d contato - %1$d chamadas perdidas de %2$d contatos + %1$d de chamadas perdidas de %2$d contatos %1$d chamadas perdidas de %2$d contatos Chamada de áudio @@ -967,7 +967,7 @@ Adicionar contato, criar ou associar-se a uma conversa em grupo ou descobrir canais Ver %1$d participante - Ver %1$d participantes + Ver %1$d de participantes Ver %1$d participantes diff --git a/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java b/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java index e5f4b9d7c83fc7463b1283c60326441d929eece2..41e4670541781b8f56d468d6dbc3d7fe25f3aca1 100644 --- a/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java +++ b/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; } diff --git a/src/quicksy/java/eu/siacs/conversations/entities/Entry.java b/src/quicksy/java/eu/siacs/conversations/entities/Entry.java index c202be4707a0344143a3e6be3553e062aaa7d33d..7e9fbcf5ebd2868c504c57f50770363559bbe669 100644 --- a/src/quicksy/java/eu/siacs/conversations/entities/Entry.java +++ b/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 { private final List jids; private final String number; @@ -47,23 +42,25 @@ public class Entry implements Comparable { return entries; } - public static String statusQuo(final Collection phoneNumberContacts, Collection systemContacts) { + public static String statusQuo( + final Collection phoneNumberContacts, + Collection systemContacts) { return statusQuo(ofPhoneNumberContactsAndContacts(phoneNumberContacts, systemContacts)); } private static String statusQuo(final List 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 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 { return new String(Base64.encode(sha1, Base64.DEFAULT)).trim(); } - private static List ofPhoneNumberContactsAndContacts(final Collection phoneNumberContacts, Collection systemContacts) { + private static List ofPhoneNumberContactsAndContacts( + final Collection phoneNumberContacts, + Collection systemContacts) { final ArrayList 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 { } private static Entry findOrCreateByPhoneNumber(final List entries, String number) { - for(Entry entry : entries) { + for (Entry entry : entries) { if (entry.number.equals(number)) { return entry; } diff --git a/src/quicksy/java/eu/siacs/conversations/ui/EnterNameActivity.java b/src/quicksy/java/eu/siacs/conversations/ui/EnterNameActivity.java index 22a21d32279eceebf3943263de246821e853d8cb..42c2d3c6fc453e4b1bab68ec011ab9f432728210 100644 --- a/src/quicksy/java/eu/siacs/conversations/ui/EnterNameActivity.java +++ b/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); } diff --git a/src/quicksy/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java b/src/quicksy/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java index 4bc18b886c9662fe2a7eb035346a69660de6eb4f..f77657ea3bc6a1c83c61d91b84feec72998de292 100644 --- a/src/quicksy/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java +++ b/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 getCountries(final Context context) { List 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 { @@ -97,5 +100,4 @@ public class PhoneNumberUtilWrapper { return name.compareTo(o.name); } } - } diff --git a/src/quicksy/res/values-be/strings.xml b/src/quicksy/res/values-be/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..a6b3daec9354f9ae75cdf8d94a67446c6227dd96 --- /dev/null +++ b/src/quicksy/res/values-be/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file