diff --git a/app/build.gradle b/app/build.gradle index 6f51d3aff..a9533af6a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -80,8 +80,8 @@ protobuf { } } -def canonicalVersionCode = 717 -def canonicalVersionName = "4.73.1" +def canonicalVersionCode = 718 +def canonicalVersionName = "4.73.2" def postFixSize = 10 def abiPostFix = ['universal' : 0, diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java index 260d6be6c..683d66817 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java @@ -35,6 +35,7 @@ public class ConversationItemFooter extends LinearLayout { private ExpirationTimerView timerView; private ImageView insecureIndicatorView; private DeliveryStatusView deliveryStatusView; + private boolean onlyShowSendingStatus; public ConversationItemFooter(Context context) { super(context); @@ -93,6 +94,11 @@ public class ConversationItemFooter extends LinearLayout { deliveryStatusView.setTint(color); } + public void setOnlyShowSendingStatus(boolean onlyShowSending, MessageRecord messageRecord) { + this.onlyShowSendingStatus = onlyShowSending; + presentDeliveryStatus(messageRecord); + } + private void presentDate(@NonNull MessageRecord messageRecord, @NonNull Locale locale) { dateView.forceLayout(); if (messageRecord.isFailed()) { @@ -173,14 +179,29 @@ public class ConversationItemFooter extends LinearLayout { } private void presentDeliveryStatus(@NonNull MessageRecord messageRecord) { - if (!messageRecord.isFailed() && !messageRecord.isPendingInsecureSmsFallback()) { - if (!messageRecord.isOutgoing()) deliveryStatusView.setNone(); - else if (messageRecord.isPending()) deliveryStatusView.setPending(); - else if (messageRecord.isRemoteRead()) deliveryStatusView.setRead(); - else if (messageRecord.isDelivered()) deliveryStatusView.setDelivered(); - else deliveryStatusView.setSent(); - } else { + if (messageRecord.isFailed() || messageRecord.isPendingInsecureSmsFallback()) { deliveryStatusView.setNone(); + return; + } + + if (onlyShowSendingStatus) { + if (messageRecord.isOutgoing() && messageRecord.isPending()) { + deliveryStatusView.setPending(); + } else { + deliveryStatusView.setNone(); + } + } else { + if (!messageRecord.isOutgoing()) { + deliveryStatusView.setNone(); + } else if (messageRecord.isPending()) { + deliveryStatusView.setPending(); + } else if (messageRecord.isRemoteRead()) { + deliveryStatusView.setRead(); + } else if (messageRecord.isDelivered()) { + deliveryStatusView.setDelivered(); + } else { + deliveryStatusView.setSent(); + } } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java index df74b7ea9..e238be817 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -420,14 +420,17 @@ public class ConversationItem extends LinearLayout implements BindableConversati bodyBubble.getBackground().setColorFilter(defaultBubbleColor, PorterDuff.Mode.MULTIPLY); footer.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_sent_text_secondary_color)); footer.setIconColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_sent_icon_color)); + footer.setOnlyShowSendingStatus(false, messageRecord); } else if (messageRecord.isRemoteDelete() || (isViewOnceMessage(messageRecord) && ViewOnceUtil.isViewed((MmsMessageRecord) messageRecord))) { bodyBubble.getBackground().setColorFilter(ThemeUtil.getThemedColor(context, R.attr.conversation_item_reveal_viewed_background_color), PorterDuff.Mode.MULTIPLY); footer.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_sent_text_secondary_color)); footer.setIconColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_sent_icon_color)); + footer.setOnlyShowSendingStatus(messageRecord.isRemoteDelete(), messageRecord); } else { bodyBubble.getBackground().setColorFilter(messageRecord.getRecipient().getColor().toConversationColor(context), PorterDuff.Mode.MULTIPLY); footer.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_received_text_secondary_color)); footer.setIconColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_received_text_secondary_color)); + footer.setOnlyShowSendingStatus(false, messageRecord); } outliner.setColor(ThemeUtil.getThemedColor(getContext(), R.attr.conversation_item_sent_text_secondary_color)); @@ -652,7 +655,7 @@ public class ConversationItem extends LinearLayout implements BindableConversati { boolean showControls = !messageRecord.isFailed(); - if (isViewOnceMessage(messageRecord)) { + if (isViewOnceMessage(messageRecord) && !messageRecord.isRemoteDelete()) { revealableStub.get().setVisibility(VISIBLE); if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE); if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); @@ -1521,7 +1524,7 @@ public class ConversationItem extends LinearLayout implements BindableConversati @Override public void onClick(@NonNull View widget) { - if (eventListener != null) { + if (eventListener != null && batchSelected.isEmpty()) { VibrateUtil.vibrateTick(context); eventListener.onGroupMemberClicked(mentionedRecipientId, conversationRecipient.get().requireGroupId()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java index 5512bd40f..e8c59aa49 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java @@ -377,10 +377,23 @@ public final class ConversationListItem extends RelativeLayout } else { alertView.setNone(); - if (thread.isPending()) deliveryStatusIndicator.setPending(); - else if (thread.isRemoteRead()) deliveryStatusIndicator.setRead(); - else if (thread.isDelivered()) deliveryStatusIndicator.setDelivered(); - else deliveryStatusIndicator.setSent(); + if (thread.getExtra() != null && thread.getExtra().isRemoteDelete()) { + if (thread.isPending()) { + deliveryStatusIndicator.setPending(); + } else { + deliveryStatusIndicator.setNone(); + } + } else { + if (thread.isPending()) { + deliveryStatusIndicator.setPending(); + } else if (thread.isRemoteRead()) { + deliveryStatusIndicator.setRead(); + } else if (thread.isDelivered()) { + deliveryStatusIndicator.setDelivered(); + } else { + deliveryStatusIndicator.setSent(); + } + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index 1dbf48096..0a42cad9c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -1079,7 +1079,7 @@ public class RecipientDatabase extends Database { .flatMap(Stream::of) .toArray(String[]::new); - try (Cursor cursor = db.query(table, columns, query, args, null, null, null)) { + try (Cursor cursor = db.query(table, columns, query, args, TABLE_NAME + "." + ID, null, null)) { while (cursor != null && cursor.moveToNext()) { out.add(getRecipientSettings(context, cursor)); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index 911c97de0..d7235a304 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -1220,10 +1220,10 @@ public class ThreadDatabase extends Database { return Extra.forMessageRequest(); } - if (record.isViewOnce()) { - return Extra.forViewOnce(); - } else if (record.isRemoteDelete()) { + if (record.isRemoteDelete()) { return Extra.forRemoteDelete(); + } else if (record.isViewOnce()) { + return Extra.forViewOnce(); } else if (record.isMms() && ((MmsMessageRecord) record).getSlideDeck().getStickerSlide() != null) { StickerSlide slide = Objects.requireNonNull(((MmsMessageRecord) record).getSlideDeck().getStickerSlide()); return Extra.forSticker(slide.getEmoji()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java index 37ccde7e9..c5440f25f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java @@ -372,6 +372,7 @@ public final class GroupsV2StateProcessor { Log.d(TAG, "Skipping profile key changes only update message"); } else { storeMessage(GroupProtoUtil.createDecryptedGroupV2Context(masterKey, new GroupMutation(previousGroupState, entry.getChange(), entry.getGroup()), null), timestamp); + timestamp++; } previousGroupState = entry.getGroup(); } @@ -476,7 +477,9 @@ public final class GroupsV2StateProcessor { IncomingTextMessage incoming = new IncomingTextMessage(sender, -1, timestamp, timestamp, "", Optional.of(groupId), 0, false); IncomingGroupUpdateMessage groupMessage = new IncomingGroupUpdateMessage(incoming, decryptedGroupV2Context); - smsDatabase.insertMessageInbox(groupMessage); + if (!smsDatabase.insertMessageInbox(groupMessage).isPresent()) { + Log.w(TAG, "Could not insert update message"); + } } } diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index ac7523086..c3bf12847 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -134,8 +134,11 @@ Odebrat skupinovou fotografii? Aktualizovat Signal + Tato verze aplikace již není podporována. Chcete-li pokračovat v odesílání a přijímání zpráv, aktualizujte na nejnovější verzi. Aktualizovat + Neaktualizovat Varování + Verze Signalu, kterou používáte, je zastaralá. Můžete zobrazit svoji historii zpráv, ale nebudete moci zprávy odesílat ani přijímat, dokud neprovedete aktualizaci. Nebyl nalezen žádný webový prohlížeč. Nenalezena žádná emailová aplikace. @@ -351,7 +354,7 @@ Ověřeno Vy - Některé kontakty nemůžou být v zastaralých skupinách. + Některé kontakty nemohou být ve starších skupinách. Profil Chyba při nastavení profilové fotografie @@ -399,6 +402,8 @@ Optimlizovat pro chybějící Google Play službu Toto zařízení nepodporuje službu Google Play. Klepnutím zakážete systémové optimalizace baterie, které způsobují nemožnost Signalu přijímat zprávy v době nečinnosti. + Tato verze Signalu je zastaralá. Proveďte aktualizaci, abyste mohli odesílat a přijímat zprávy. + Aktualizovat nyní Sdílet s Vícero příloh je podporováno pouze pro obrázky a videa @@ -418,7 +423,7 @@ \"%1$s\" přidán do \"%2$s\". Přidat do skupiny Přidat do skupin - Tohoto člověka nemůžete přidat do zastaralých skupin. + Tohoto člověka nemůžete přidat do starších skupin. Přidat Vyberte nového administrátora. @@ -498,7 +503,7 @@ Odmítnut \"%1$s\" Hotovo - Tohoto člověka nemůžete přidat do zastaralých skupin. + Tohoto člověka nemůžete přidat do starších skupin. Přidat \"%1$s\" do \"%2$s\"? Přidat %3$d členy do \"%2$s\"? @@ -527,12 +532,12 @@ %d členů nepodporuje nové skupiny, takže tato skupina bude ve starší verzi. - Bude vytvořena starší verze skupiny, protože \"%1$s\" používá zastaralou verzi Signalu. Novou verzi skupiny můžete s ním vytvořit, až si zaktualizuje Signal, nebo jej před vytvořením skupiny odstraňte. + Bude vytvořena starší skupina, protože \"%1$s\" používá zastaralou verzi Signalu. Novou verzi skupiny můžete s ním vytvořit, až si zaktualizuje Signal, nebo jej před vytvořením skupiny odstraňte. - Bude vytvořena starší verze skupiny, protože %1$d člen používá zastaralou verzi Signalu. Novou verzi skupiny můžete s ním vytvořit, až si zaktualizuje Signal, nebo jej před vytvořením skupiny odstraňte. - Bude vytvořena starší verze skupiny, protože %1$d členové používají zastaralou verzi Signalu. Novou verzi skupiny můžete s nimi vytvořit, až si zaktualizují Signal, nebo je před vytvořením skupiny odstraňte. - Bude vytvořena starší verze skupiny, protože %1$d členů používá zastaralou verzi Signalu. Novou verzi skupiny můžete s nimi vytvořit, až si zaktualizují Signal, nebo je před vytvořením skupiny odstraňte. - Bude vytvořena starší verze skupiny, protože %1$d členů používá zastaralou verzi Signalu. Novou verzi skupiny můžete s nimi vytvořit, až si zaktualizují Signal, nebo je před vytvořením skupiny odstraňte. + Bude vytvořena starší skupina, protože %1$d člen používá zastaralou verzi Signalu. Novou verzi skupiny můžete s ním vytvořit, až si zaktualizuje Signal, nebo jej před vytvořením skupiny odstraňte. + Bude vytvořena starší skupina, protože %1$d členové používají zastaralou verzi Signalu. Novou verzi skupiny můžete s nimi vytvořit, až si zaktualizují Signal, nebo je před vytvořením skupiny odstraňte. + Bude vytvořena starší skupina, protože %1$d členů používá zastaralou verzi Signalu. Novou verzi skupiny můžete s nimi vytvořit, až si zaktualizují Signal, nebo je před vytvořením skupiny odstraňte. + Bude vytvořena starší skupina, protože %1$d členů používá zastaralou verzi Signalu. Novou verzi skupiny můžete s nimi vytvořit, až si zaktualizují Signal, nebo je před vytvořením skupiny odstraňte. Zmizení zpráv @@ -540,6 +545,8 @@ Požadavky & pozvánky členů Přidat členy Upravit informace o skupině + Kdo může přidávat nové členy? + Kdo může upravovat informace o této skupině? Skupinový odkaz Zablokovat skupinu Odblokovat skupinu @@ -573,6 +580,9 @@ Nepodařilo se aktualizovat skupinu, kvůli chybě sítě, prosím, zkuste to později Upravit název a obrázek Starší skupina + Toto je starší skupina. Funkce jako správci skupin jsou dostupné pouze pro nové skupiny. + Toto je nezabezpečená MMS skupina. Chcete-li komunikovat soukromě a mít přístup k funkcím jako jsou názvy skupin, pozvěte své kontakty do Signalu. + Pozvat nyní Upozornit mne na zmínky. Přijmout upozornění při zmínce ve ztišených rozhovorech? @@ -673,6 +683,7 @@ Připojení ke skupině pomocí odkazu ještě není v Signalu podporováno. Tato funkce bude zahrnuta v nadcházející aktualizaci. Verze Signalu, kterou používáte, nepodporuje skupinové odkazy. Aktualizujte na nejnovější verzi pro připojení k této skupině pomocí odkazu. Aktualizovat Signal + Na jednom nebo více vašich propojených zařízeních běží verze Signalu, která nepodporuje skupinové odkazy. Aktualizujte Signal na propojeném zařízení či zařízeních, abyste se mohli připojit k této skupině. Skupinový odkaz je neplatný Přidat \"%1$s\" do skupiny? @@ -1026,6 +1037,14 @@ Platnost zprávy neskončí. Zprávy odeslané a přijaté v této konverzaci zmizí za %s po jejich shlédnutí. + Aktualizovat nyní + Tato verze Signalu dnes vyprší. Aktualizujte na nejnovější verzi. + + Tato verze Signalu vyprší zítra. Aktualizujte na nejnovější verzi. + Tato verze Signalu vyprší za %d dny. Aktualizujte na nejnovější verzi. + Tato verze Signalu vyprší za %d dnů. Aktualizujte na nejnovější verzi. + Tato verze Signalu vyprší za %d dnů. Aktualizujte na nejnovější verzi. + Zadat heslo Ikona Signal @@ -1105,10 +1124,21 @@ Signal potřebuje přístup k vašemu fotoaparátu, pro volání %1$s Signal %1$s Volání… + Skupinový hovor Signal hlasový hovor… Signal videohovor… + Zahájit hovor + Skupinový hovor + Zobrazit účastníky + Vaše video je vypnuté + + V tomto hovoru - %1$d člověk + V tomto hovoru - %1$d lidi + V tomto hovoru - %1$d lidi + V tomto hovoru - %1$d lidí + Zvolte svou zemi Zadejte kód @@ -2200,9 +2230,15 @@ Obdržen požadavek na výměnu klíčů pro neplatnou verzi protokolu. Připomeneme se vám později. Vytvoření PIN bude povinné během %1$d dnů. Připomeneme se vám později. Potvrzení vašeho PIN bude povinné během %1$d dnů. + Řekněte Signalu, co si myslíte + Aby byl Signal nejlepší aplikací pro zasílání zpráv na planetě, rádi bychom získali vaši zpětnou vazbu. Zjistit více Zavřít + Průzkum Signalu + Věříme v soukromí.

Signal vás nesleduje ani nesbírá vaše data. Abychom Signal vylepšili pro všechny, spoléháme na zpětnou vazbu uživatelů, a rádi bychom ji od vás získali.

Pořádáme anketu, abychom porozuměli, jak Signal používáte. Naše anketa neshromažďuje žádná data, která by vás identifikovala. Pokud byste měli zájem sdílet další zpětnou vazbu, budete mít možnost poskytnout kontaktní informace.

Jestli máte pár minut a zpětnou vazbu, kterou můžete nabídnout, rádi ji od vás získáme.

]]>
+ Zúčastnit se ankety Ne díky + Anketu pořádá Surveygizmo na zabezpečené doméně surveys.signalusers.org Ikona přenosu Načítání… @@ -2342,6 +2378,8 @@ Obdržen požadavek na výměnu klíčů pro neplatnou verzi protokolu. Nezabezpečený hlasový hovor Videohovor Odebrat %1$s jako správce skupiny? + \"%1$s\" bude moci upravovat tuto skupinu a její členy. + Odstranit %1$s ze skupiny? Odebrat Zkopírováno do schránky Správce @@ -2351,6 +2389,9 @@ Obdržen požadavek na výměnu klíčů pro neplatnou verzi protokolu. Starší a nové skupiny Co jsou starší skupiny? Starší skupiny neumí vlastnosti nových skupin, jako je nastavení administrátorů a popisnější aktualizace skupin, + Mohu aktualizovat starší skupinu? + Starší skupiny zatím nelze aktualizovat na nové skupiny, ale můžete vytvořit novou skupinu se stejnými členy, pokud používají poslední verzi Signalu. + Signal nabídne možnost aktualizovat starší skupiny v budoucnosti. Sdílet přes Signal Kopírovat diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 7850eaa62..4e03c4220 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -285,7 +285,7 @@ Eliminar Eliminando mensajes … Eliminar para mi - Eliminar para tod@s + Eliminar del chat No se encuentra el mensaje original El mensaje original ya no está disponible @@ -2265,7 +2265,7 @@ Se recibió un mensaje de intercambio de claves para una versión no válida del Grupos nuevos y antiguos ¿Qué son los grupos antiguos? Los grupos antiguos no son compatibles con las características de los grupos nuevos como admins o actualizaciones más descriptivas. - ¿Puedo actualizar un grupo antiguo? + ¿Cómo actualizo un grupo antiguo? Los grupos antiguos todavía no pueden convertirse en grupos nuevos, pero puedes crear un grupo nuevo con la misma gente, si tod@s usan la versión más reciente de Signal. Signal ofrecerá la manera de actualizar un grupo antiguo en el futuro. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index b9da09f6e..ef2063c50 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -283,7 +283,7 @@ Suppression des messages… Supprimer pour moi Supprimer pour tous - + Le message original est introuvable Le message original n’est plus disponible Échec d’ouverture du message diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index ed296a23a..ca8c21841 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -1723,7 +1723,7 @@ Uvezi sigurnosnu kopiju običnog teksta. Kompatibilno s \'SMSBackup And Restore\ Sada možete odabrati želite li prihvatiti novi razgovor. Vidjet ćete opcije \"Prihvati\", \"Izbriši\" ili \"Blokiraj\". Pomoć - Jeste li već pročitali naša ČČP? + Jeste li već pročitali naša ČPP? Sljedeće Kontaktirajte nas Recite nam što se događa diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index b0cb47f69..c51a21a88 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -533,10 +533,15 @@ Aktivert Deaktivert Forvalgt + Krev administratorgodkjenning for nye medlemmer som blir med via gruppelinken. + Er du sikker på at du vil tilbakestille gruppelenken? Folk vil ikke lenger kunne bli med i gruppen ved hjelp av den nåværende lenken. + Folk som skanner denne koden vil være i stand til å bli med i gruppen din. Administratorer vil fremdeles trenge å godkjenne nye medlemmer om denne opsjonen er påslått. + Bli med Det oppstod en nettverksfeil. + Ønsker du å bli medlem i denne gruppen samt dele navnet og bildet ditt med medlemmene? Oppdater Signal @@ -773,9 +778,12 @@ Du resatte gruppelenken. + Du ble medlem i gruppen via gruppelinken. + Du sendte en forespørsel om å bli med i gruppen. + Du kansellerte forespørselen din om å bli med i gruppen. Sikkerhetsnummeret ditt for %s er endret. Du markerte sikkerhetsnummeret for %s som bekreftet @@ -2069,7 +2077,9 @@ Mottok nøkkelutvekslingsmelding for ugyldig protokollversion.
Hva er \"legacy\" grupper? Legacy grupper er grupper som ikke er kompatible med nye gruppefunksjoner som administrator og mer beskrivende gruppeoppdateringer. + Del via Signal Kopier + QR-kode Del Kopiert til utklippstavle diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index a3c76961f..d78459806 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -432,7 +432,7 @@ Tüm bağlantıları önizle Şimdi, gönderdiğiniz iletiler için doğrudan web sitelerinden bağlantı önizlemelerini alabilirsiniz. - Bağlantı ön izlemesi mevcut değil + Bağlantı önizlemesi mevcut değil Grup bağlantısı etkin değil %1$s · %2$s @@ -621,7 +621,7 @@ Gruba katılmadan önce bu grubun bir yöneticisinin isteğinizi onaylaması gerekiyor. Katılma isteğinde bulunduğunuzda, isminiz ve fotoğrafınız üyeleriyle paylaşılacaktır. Grup · %1$d üye - Group · %1$d üye + Grup · %1$d üye Grup bağlantıları çok yakında @@ -632,8 +632,8 @@ Bağlı aygıtlarınızdan bir veya daha fazla grup bağlantılarını desteklemeyen Signal sürümü kullanıyor. Bu gruba katılabilmek için bağlı aygıtlarınızdaki Signal\'i güncelleyin. Grup bağlantısı geçersiz - \"%1$s\" gruba ekle? - \"%1$s\" kişisinin isteğini reddet? + \"%1$s\" gruba eklensin mi? + \"%1$s\" kişisinin isteği reddedilsin mi? Ekle Reddet @@ -922,8 +922,8 @@ %1$d üye (+%2$d davetli) - %d ek grup - %d ek grup + %d diğer grup + %d diğer grup Parolalar uyuşmuyor! @@ -1044,7 +1044,7 @@ Arama Başlat Grup Araması Katılımcıları Görüntüle - Videonuz Kapalı + Görüntünüz kapalı Bu aramada · %1$d kişi @@ -1278,7 +1278,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. İletiyi sil? Bu eylem bu iletiyi kalıcı olarak silecektir. %1$s --> %2$s - Ortam artık mevcut değil. + İçerik artık mevcut değil. %2$d konuşmadan %1$d yeni ileti En yeni ileti: %1$s @@ -1289,7 +1289,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. İleti tesliminde hata. Tümünü okundu işaretle Okundu işaretle - Bu bildirimleri kapatın + Bu bildirimleri kapat İçerik iletisi Çıkartma Tek görümlük görüntü @@ -1734,7 +1734,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. Gelen tüm içerik iletileri için Signal kullanın Enter tuşu ile gönder Enter tuşuna basmak metin iletilerini gönderecektir - Bağlantı ön izlemelerini oluştur + Bağlantı önizlemelerini oluştur Gönderdiğiniz iletiler için doğrudan web sitelerinden bağlantı önizlemelerini al. Kimlik seçin Kişi girdinizi kişi listesinden seçin. diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Api.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Api.java index a01c04aaf..2fa821bdd 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Api.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Api.java @@ -27,6 +27,7 @@ import java.io.IOException; import java.security.SecureRandom; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.UUID; @@ -97,9 +98,19 @@ public final class GroupsV2Api { GroupsV2AuthorizationString authorization) throws IOException, InvalidGroupStateException, VerificationFailedException { - GroupChanges group = socket.getGroupsV2GroupHistory(fromRevision, authorization); + List changesList = new LinkedList<>(); + PushServiceSocket.GroupHistory group; + + do { + group = socket.getGroupsV2GroupHistory(fromRevision, authorization); + + changesList.addAll(group.getGroupChanges().getGroupChangesList()); + + if (group.hasMore()) { + fromRevision = group.getNextPageStartGroupRevision(); + } + } while (group.hasMore()); - List changesList = group.getGroupChangesList(); ArrayList result = new ArrayList<>(changesList.size()); GroupsV2Operations.GroupOperations groupOperations = groupsOperations.forGroup(groupSecretParams); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/ContentRange.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/ContentRange.java new file mode 100644 index 000000000..b8bded21f --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/ContentRange.java @@ -0,0 +1,49 @@ +package org.whispersystems.signalservice.internal.push; + +import org.whispersystems.libsignal.util.guava.Optional; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class ContentRange { + private static final Pattern PATTERN = Pattern.compile("versions (\\d+)-(\\d+)\\/(\\d+)"); + + private final int rangeStart; + private final int rangeEnd; + private final int totalSize; + + /** + * Parses a content range header. + */ + public static Optional parse(String header) { + if (header != null) { + Matcher matcher = PATTERN.matcher(header); + + if (matcher.matches()) { + return Optional.of(new ContentRange(Integer.parseInt(matcher.group(1)), + Integer.parseInt(matcher.group(2)), + Integer.parseInt(matcher.group(3)))); + } + } + + return Optional.absent(); + } + + private ContentRange(int rangeStart, int rangeEnd, int totalSize) { + this.rangeStart = rangeStart; + this.rangeEnd = rangeEnd; + this.totalSize = totalSize; + } + + public int getRangeStart() { + return rangeStart; + } + + public int getRangeEnd() { + return rangeEnd; + } + + public int getTotalSize() { + return totalSize; + } +} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java index 414ef0b41..0d598fe73 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java @@ -404,12 +404,7 @@ public class PushServiceSocket { Response response = makeServiceRequest(String.format(MESSAGE_PATH, ""), "GET", (RequestBody) null, NO_HEADERS, NO_HANDLER, Optional.absent()); validateServiceResponse(response); - List envelopes; - try { - envelopes = JsonUtil.fromJson(readBodyString(response.body()), SignalServiceEnvelopeEntityList.class).getMessages(); - } catch (IOException e) { - throw new PushNetworkException(e); - } + List envelopes = readBodyJson(response.body(), SignalServiceEnvelopeEntityList.class).getMessages(); long serverDeliveredTimestamp = 0; try { @@ -1346,11 +1341,7 @@ public class PushServiceSocket { @Override public void onResponse(Call call, Response response) { try (ResponseBody body = validateServiceResponse(response).body()) { - try { - bodyFuture.set(readBodyString(body)); - } catch (IOException e) { - throw new PushNetworkException(e); - } + bodyFuture.set(readBodyString(body)); } catch (IOException e) { bodyFuture.setException(e); } @@ -1392,9 +1383,8 @@ public class PushServiceSocket { } private Response validateServiceResponse(Response response) throws NonSuccessfulResponseCodeException, PushNetworkException { - int responseCode = response.code(); - String responseMessage = response.message(); - ResponseBody responseBody = response.body(); + int responseCode = response.code(); + String responseMessage = response.message(); switch (responseCode) { case 413: @@ -1405,58 +1395,23 @@ public class PushServiceSocket { case 404: throw new NotFoundException("Not found"); case 409: - MismatchedDevices mismatchedDevices; - - try { - mismatchedDevices = JsonUtil.fromJson(readBodyString(responseBody), MismatchedDevices.class); - } catch (JsonProcessingException e) { - Log.w(TAG, e); - throw new NonSuccessfulResponseCodeException("Bad response: " + responseCode + " " + responseMessage); - } catch (IOException e) { - throw new PushNetworkException(e); - } + MismatchedDevices mismatchedDevices = readResponseJson(response, MismatchedDevices.class); throw new MismatchedDevicesException(mismatchedDevices); case 410: - StaleDevices staleDevices; - - try { - staleDevices = JsonUtil.fromJson(readBodyString(responseBody), StaleDevices.class); - } catch (JsonProcessingException e) { - throw new NonSuccessfulResponseCodeException("Bad response: " + responseCode + " " + responseMessage); - } catch (IOException e) { - throw new PushNetworkException(e); - } + StaleDevices staleDevices = readResponseJson(response, StaleDevices.class); throw new StaleDevicesException(staleDevices); case 411: - DeviceLimit deviceLimit; - - try { - deviceLimit = JsonUtil.fromJson(readBodyString(responseBody), DeviceLimit.class); - } catch (JsonProcessingException e) { - throw new NonSuccessfulResponseCodeException("Bad response: " + responseCode + " " + responseMessage); - } catch (IOException e) { - throw new PushNetworkException(e); - } + DeviceLimit deviceLimit = readResponseJson(response, DeviceLimit.class); throw new DeviceLimitExceededException(deviceLimit); case 417: throw new ExpectationFailedException(); case 423: - RegistrationLockFailure accountLockFailure; - - try { - accountLockFailure = JsonUtil.fromJson(readBodyString(responseBody), RegistrationLockFailure.class); - } catch (JsonProcessingException e) { - Log.w(TAG, e); - throw new NonSuccessfulResponseCodeException("Bad response: " + responseCode + " " + responseMessage); - } catch (IOException e) { - throw new PushNetworkException(e); - } - - AuthCredentials credentials = accountLockFailure.backupCredentials; - String basicStorageCredentials = credentials != null ? credentials.asBasic() : null; + RegistrationLockFailure accountLockFailure = readResponseJson(response, RegistrationLockFailure.class); + AuthCredentials credentials = accountLockFailure.backupCredentials; + String basicStorageCredentials = credentials != null ? credentials.asBasic() : null; throw new LockedException(accountLockFailure.length, accountLockFailure.timeRemaining, @@ -1630,6 +1585,12 @@ public class PushServiceSocket { private ResponseBody makeStorageRequest(String authorization, String path, String method, RequestBody body, ResponseCodeHandler responseCodeHandler) throws PushNetworkException, NonSuccessfulResponseCodeException + { + return makeStorageRequestResponse(authorization, path, method, body, responseCodeHandler).body(); + } + + private Response makeStorageRequestResponse(String authorization, String path, String method, RequestBody body, ResponseCodeHandler responseCodeHandler) + throws PushNetworkException, NonSuccessfulResponseCodeException { ConnectionHolder connectionHolder = getRandom(storageClients, random); OkHttpClient okHttpClient = connectionHolder.getClient() @@ -1663,7 +1624,7 @@ public class PushServiceSocket { response = call.execute(); if (response.isSuccessful() && response.code() != 204) { - return response.body(); + return response; } } catch (IOException e) { throw new PushNetworkException(e); @@ -1790,6 +1751,9 @@ public class PushServiceSocket { * Converts {@link IOException} on body byte reading to {@link PushNetworkException}. */ private static byte[] readBodyBytes(ResponseBody response) throws PushNetworkException { + if (response == null) { + throw new PushNetworkException("No body!"); + } try { return response.bytes(); } catch (IOException e) { @@ -1797,14 +1761,52 @@ public class PushServiceSocket { } } - private static String readBodyString(ResponseBody body) throws IOException { - if (body != null) { + /** + * Converts {@link IOException} on body reading to {@link PushNetworkException}. + */ + private static String readBodyString(ResponseBody body) throws PushNetworkException { + if (body == null) { + throw new PushNetworkException("No body!"); + } + + try { return body.string(); - } else { - throw new IOException("No body!"); + } catch (IOException e) { + throw new PushNetworkException(e); } } + /** + * Converts {@link IOException} on body reading to {@link PushNetworkException}. + * {@link IOException} during json parsing is converted to a {@link NonSuccessfulResponseCodeException} + */ + private static T readBodyJson(ResponseBody body, Class clazz) + throws PushNetworkException, NonSuccessfulResponseCodeException + { + String json = readBodyString(body); + try { + return JsonUtil.fromJson(json, clazz); + } catch (JsonProcessingException e) { + Log.w(TAG, e); + throw new NonSuccessfulResponseCodeException("Unable to parse entity"); + } catch (IOException e) { + throw new PushNetworkException(e); + } + } + + /** + * Converts {@link IOException} on body reading to {@link PushNetworkException}. + * {@link IOException} during json parsing is converted to a {@link NonSuccessfulResponseCodeException} with response code detail. + */ + private static T readResponseJson(Response response, Class clazz) + throws PushNetworkException, NonSuccessfulResponseCodeException + { + try { + return readBodyJson(response.body(), clazz); + } catch (NonSuccessfulResponseCodeException e) { + throw new NonSuccessfulResponseCodeException("Bad response: " + response.code() + " " + response.message()); + } + } private static class GcmRegistrationId { @@ -1984,16 +1986,31 @@ public class PushServiceSocket { return GroupChange.parseFrom(readBodyBytes(response)); } - public GroupChanges getGroupsV2GroupHistory(int fromVersion, GroupsV2AuthorizationString authorization) + public GroupHistory getGroupsV2GroupHistory(int fromVersion, GroupsV2AuthorizationString authorization) throws NonSuccessfulResponseCodeException, PushNetworkException, InvalidProtocolBufferException { - ResponseBody response = makeStorageRequest(authorization.toString(), - String.format(Locale.US, GROUPSV2_GROUP_CHANGES, fromVersion), - "GET", - null, - GROUPS_V2_GET_LOGS_HANDLER); + Response response = makeStorageRequestResponse(authorization.toString(), + String.format(Locale.US, GROUPSV2_GROUP_CHANGES, fromVersion), + "GET", + null, + GROUPS_V2_GET_LOGS_HANDLER); - return GroupChanges.parseFrom(readBodyBytes(response)); + GroupChanges groupChanges = GroupChanges.parseFrom(readBodyBytes(response.body())); + + if (response.code() == 206) { + String contentRangeHeader = response.header("Content-Range"); + Optional contentRange = ContentRange.parse(contentRangeHeader); + + if (contentRange.isPresent()) { + Log.i(TAG, "Additional logs for group: " + contentRangeHeader); + return new GroupHistory(groupChanges, contentRange); + } else { + Log.w(TAG, "Unable to parse Content-Range header: " + contentRangeHeader); + throw new NonSuccessfulResponseCodeException("Unable to parse content range header on 206"); + } + } + + return new GroupHistory(groupChanges, Optional.absent()); } public GroupJoinInfo getGroupJoinInfo(Optional groupLinkPassword, GroupsV2AuthorizationString authorization) @@ -2009,6 +2026,31 @@ public class PushServiceSocket { return GroupJoinInfo.parseFrom(readBodyBytes(response)); } + public static final class GroupHistory { + private final GroupChanges groupChanges; + private final Optional contentRange; + + public GroupHistory(GroupChanges groupChanges, Optional contentRange) { + this.groupChanges = groupChanges; + this.contentRange = contentRange; + } + + public GroupChanges getGroupChanges() { + return groupChanges; + } + + public boolean hasMore() { + return contentRange.isPresent(); + } + + /** + * Valid iff {@link #hasMore()}. + */ + public int getNextPageStartGroupRevision() { + return contentRange.get().getRangeEnd() + 1; + } + } + private final class ResumeInfo { private final String contentRange; private final long contentStart; diff --git a/libsignal/service/src/test/java/org/whispersystems/signalservice/internal/push/ContentRange_parse_Test.java b/libsignal/service/src/test/java/org/whispersystems/signalservice/internal/push/ContentRange_parse_Test.java new file mode 100644 index 000000000..496835147 --- /dev/null +++ b/libsignal/service/src/test/java/org/whispersystems/signalservice/internal/push/ContentRange_parse_Test.java @@ -0,0 +1,49 @@ +package org.whispersystems.signalservice.internal.push; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.Assert.assertEquals; + +@RunWith(Parameterized.class) +public final class ContentRange_parse_Test { + + @Parameterized.Parameter(0) + public String input; + + @Parameterized.Parameter(1) + public int expectedRangeStart; + + @Parameterized.Parameter(2) + public int expectedRangeEnd; + + @Parameterized.Parameter(3) + public int expectedSize; + + @Parameterized.Parameters(name = "Content-Range: \"{0}\"") + public static Collection data() { + return Arrays.asList(new Object[][]{ + { "versions 1-2/3", 1, 2, 3 }, + { "versions 23-45/67", 23, 45, 67 }, + }); + } + + @Test + public void rangeStart() { + assertEquals(expectedRangeStart, ContentRange.parse(input).get().getRangeStart()); + } + + @Test + public void rangeEnd() { + assertEquals(expectedRangeEnd, ContentRange.parse(input).get().getRangeEnd()); + } + + @Test + public void totalSize() { + assertEquals(expectedSize, ContentRange.parse(input).get().getTotalSize()); + } +} diff --git a/libsignal/service/src/test/java/org/whispersystems/signalservice/internal/push/ContentRange_parse_withInvalidStrings_Test.java b/libsignal/service/src/test/java/org/whispersystems/signalservice/internal/push/ContentRange_parse_withInvalidStrings_Test.java new file mode 100644 index 000000000..9abffba9d --- /dev/null +++ b/libsignal/service/src/test/java/org/whispersystems/signalservice/internal/push/ContentRange_parse_withInvalidStrings_Test.java @@ -0,0 +1,34 @@ +package org.whispersystems.signalservice.internal.push; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.Assert.assertFalse; + +@RunWith(Parameterized.class) +public final class ContentRange_parse_withInvalidStrings_Test { + + @Parameterized.Parameter(0) + public String input; + + @Parameterized.Parameters(name = "Content-Range: \"{0}\"") + public static Collection data() { + return Arrays.asList(new Object[][]{ + { null }, + { "" }, + { "23-45/67" }, + { "ersions 23-45/67" }, + { "versions 23-45" }, + { "versions a-b/c" } + }); + } + + @Test + public void parse_should_be_absent() { + assertFalse(ContentRange.parse(input).isPresent()); + } +}