Copione merged onto master

master
blallo 2020-10-10 00:00:21 +02:00
commit 41e5eba935
18 changed files with 387 additions and 111 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

@ -285,7 +285,7 @@
<string name="ConversationFragment_deleting">Eliminar</string>
<string name="ConversationFragment_deleting_messages">Eliminando mensajes …</string>
<string name="ConversationFragment_delete_for_me">Eliminar para mi</string>
<string name="ConversationFragment_delete_for_everyone">Eliminar para tod@s</string>
<string name="ConversationFragment_delete_for_everyone">Eliminar del chat</string>
<!-- Removed by excludeNonTranslatables <string name="ConversationFragment_this_message_will_be_permanently_deleted_for_everyone">Este mensaje se eliminará para tod@s sus participantes. En el chat se mostrará que lo has eliminado. </string> -->
<string name="ConversationFragment_quoted_message_not_found">No se encuentra el mensaje original </string>
<string name="ConversationFragment_quoted_message_no_longer_available">El mensaje original ya no está disponible</string>
@ -2265,7 +2265,7 @@ Se recibió un mensaje de intercambio de claves para una versión no válida del
<string name="GroupsLearnMore_legacy_vs_new_groups">Grupos nuevos y antiguos</string>
<string name="GroupsLearnMore_what_are_legacy_groups">¿Qué son los grupos antiguos?</string>
<string name="GroupsLearnMore_paragraph_1">Los grupos antiguos no son compatibles con las características de los grupos nuevos como admins o actualizaciones más descriptivas.</string>
<string name="GroupsLearnMore_can_i_upgrade_a_legacy_group">¿Puedo actualizar un grupo antiguo?</string>
<string name="GroupsLearnMore_can_i_upgrade_a_legacy_group">¿Cómo actualizo un grupo antiguo?</string>
<string name="GroupsLearnMore_paragraph_2">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.</string>
<string name="GroupsLearnMore_paragraph_3">Signal ofrecerá la manera de actualizar un grupo antiguo en el futuro.</string>
<!--GroupLinkBottomSheetDialogFragment-->

View File

@ -283,7 +283,7 @@
<string name="ConversationFragment_deleting_messages">Suppression des messages…</string>
<string name="ConversationFragment_delete_for_me">Supprimer pour moi</string>
<string name="ConversationFragment_delete_for_everyone">Supprimer pour tous</string>
<!-- Removed by excludeNonTranslatables <string name="ConversationFragment_this_message_will_be_permanently_deleted_for_everyone">Ce message sera irrémédiablement supprimé pour tous les membres de la conversation. Les membres pourront voir que vous avez supprimé un message.</string> -->
<!-- Removed by excludeNonTranslatables <string name="ConversationFragment_this_message_will_be_permanently_deleted_for_everyone">Ce message sera irrémédiablement supprimé pour tout le monde dans la conversation. Les membres pourront voir que vous avez supprimé un message.</string> -->
<string name="ConversationFragment_quoted_message_not_found">Le message original est introuvable</string>
<string name="ConversationFragment_quoted_message_no_longer_available">Le message original nest plus disponible</string>
<string name="ConversationFragment_failed_to_open_message">Échec douverture du message</string>

View File

@ -1723,7 +1723,7 @@ Uvezi sigurnosnu kopiju običnog teksta. Kompatibilno s \'SMSBackup And Restore\
<string name="MessageRequestsMegaphone__you_can_now_choose_whether_to_accept">Sada možete odabrati želite li prihvatiti novi razgovor. Vidjet ćete opcije \"Prihvati\", \"Izbriši\" ili \"Blokiraj\".</string>
<!--HelpFragment-->
<string name="HelpFragment__help">Pomoć</string>
<string name="HelpFragment__have_you_read_our_faq_yet">Jeste li već pročitali naša ČČP?</string>
<string name="HelpFragment__have_you_read_our_faq_yet">Jeste li već pročitali naša ČPP?</string>
<string name="HelpFragment__next">Sljedeće</string>
<string name="HelpFragment__contact_us">Kontaktirajte nas</string>
<string name="HelpFragment__tell_us_whats_going_on">Recite nam što se događa</string>

View File

@ -533,10 +533,15 @@
<string name="ShareableGroupLinkDialogFragment__enabled">Aktivert</string>
<string name="ShareableGroupLinkDialogFragment__disabled">Deaktivert</string>
<string name="ShareableGroupLinkDialogFragment__default">Forvalgt</string>
<string name="ShareableGroupLinkDialogFragment__require_an_admin_to_approve_new_members_joining_via_the_group_link">Krev administratorgodkjenning for nye medlemmer som blir med via gruppelinken.</string>
<string name="ShareableGroupLinkDialogFragment__are_you_sure_you_want_to_reset_the_group_link">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.</string>
<!--GroupLinkShareQrDialogFragment-->
<string name="GroupLinkShareQrDialogFragment__people_who_scan_this_code_will">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.</string>
<!--GV2 Invite Revoke confirmation dialog-->
<!--GroupJoinBottomSheetDialogFragment-->
<string name="GroupJoinBottomSheetDialogFragment_join">Bli med</string>
<string name="GroupJoinBottomSheetDialogFragment_encountered_a_network_error">Det oppstod en nettverksfeil.</string>
<string name="GroupJoinBottomSheetDialogFragment_direct_join">Ønsker du å bli medlem i denne gruppen samt dele navnet og bildet ditt med medlemmene?</string>
<!--GroupJoinUpdateRequiredBottomSheetDialogFragment-->
<string name="GroupJoinUpdateRequiredBottomSheetDialogFragment_update_signal">Oppdater Signal</string>
<!--GV2 Request confirmation dialog-->
@ -773,9 +778,12 @@
<!--GV2 group link reset-->
<string name="MessageRecord_you_reset_the_group_link">Du resatte gruppelenken.</string>
<!--GV2 group link joins-->
<string name="MessageRecord_you_joined_the_group_via_the_group_link">Du ble medlem i gruppen via gruppelinken.</string>
<!--GV2 group link requests-->
<string name="MessageRecord_you_sent_a_request_to_join_the_group">Du sendte en forespørsel om å bli med i gruppen.</string>
<!--GV2 group link approvals-->
<!--GV2 group link deny-->
<string name="MessageRecord_you_canceled_your_request_to_join_the_group">Du kansellerte forespørselen din om å bli med i gruppen.</string>
<!--End of GV2 specific update messages-->
<string name="MessageRecord_your_safety_number_with_s_has_changed">Sikkerhetsnummeret ditt for %s er endret.</string>
<string name="MessageRecord_you_marked_your_safety_number_with_s_verified">Du markerte sikkerhetsnummeret for %s som bekreftet</string>
@ -2069,7 +2077,9 @@ Mottok nøkkelutvekslingsmelding for ugyldig protokollversion.</string>
<string name="GroupsLearnMore_what_are_legacy_groups">Hva er \"legacy\" grupper?</string>
<string name="GroupsLearnMore_paragraph_1">Legacy grupper er grupper som ikke er kompatible med nye gruppefunksjoner som administrator og mer beskrivende gruppeoppdateringer.</string>
<!--GroupLinkBottomSheetDialogFragment-->
<string name="GroupLinkBottomSheet_share_via_signal">Del via Signal</string>
<string name="GroupLinkBottomSheet_copy">Kopier</string>
<string name="GroupLinkBottomSheet_qr_code">QR-kode</string>
<string name="GroupLinkBottomSheet_share">Del</string>
<string name="GroupLinkBottomSheet_copied_to_clipboard">Kopiert til utklippstavle</string>
<!--EOF-->

View File

@ -432,7 +432,7 @@
<string name="LinkPreviewsMegaphone_preview_any_link">Tüm bağlantıları önizle</string>
<string name="LinkPreviewsMegaphone_you_can_now_retrieve_link_previews_directly_from_any_website">Şimdi, gönderdiğiniz iletiler için doğrudan web sitelerinden bağlantı önizlemelerini alabilirsiniz.</string>
<!--LinkPreviewView-->
<string name="LinkPreviewView_no_link_preview_available">Bağlantı ön izlemesi mevcut değil</string>
<string name="LinkPreviewView_no_link_preview_available">Bağlantı önizlemesi mevcut değil</string>
<string name="LinkPreviewView_this_group_link_is_not_active">Grup bağlantısı etkin değil</string>
<string name="LinkPreviewView_domain_date">%1$s · %2$s</string>
<!--LinkPreviewRepository-->
@ -621,7 +621,7 @@
<string name="GroupJoinBottomSheetDialogFragment_admin_approval_needed">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.</string>
<plurals name="GroupJoinBottomSheetDialogFragment_group_dot_d_members">
<item quantity="one">Grup · %1$d üye</item>
<item quantity="other">Group · %1$d üye</item>
<item quantity="other">Grup · %1$d üye</item>
</plurals>
<!--GroupJoinUpdateRequiredBottomSheetDialogFragment-->
<string name="GroupJoinUpdateRequiredBottomSheetDialogFragment_group_links_coming_soon">Grup bağlantıları çok yakında</string>
@ -632,8 +632,8 @@
<string name="GroupJoinUpdateRequiredBottomSheetDialogFragment_update_linked_device_message">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.</string>
<string name="GroupJoinUpdateRequiredBottomSheetDialogFragment_group_link_is_not_valid">Grup bağlantısı geçersiz</string>
<!--GV2 Request confirmation dialog-->
<string name="RequestConfirmationDialog_add_s_to_the_group">\"%1$s\" gruba ekle?</string>
<string name="RequestConfirmationDialog_deny_request_from_s">\"%1$s\" kişisinin isteğini reddet?</string>
<string name="RequestConfirmationDialog_add_s_to_the_group">\"%1$s\" gruba eklensin mi?</string>
<string name="RequestConfirmationDialog_deny_request_from_s">\"%1$s\" kişisinin isteği reddedilsin mi?</string>
<string name="RequestConfirmationDialog_add">Ekle</string>
<string name="RequestConfirmationDialog_deny">Reddet</string>
<!--CropImageActivity-->
@ -922,8 +922,8 @@
<item quantity="other">%1$d üye (+%2$d davetli)</item>
</plurals>
<plurals name="MessageRequestProfileView_member_of_d_additional_groups">
<item quantity="one">%d ek grup</item>
<item quantity="other">%d ek grup</item>
<item quantity="one">%d diğer grup</item>
<item quantity="other">%d diğer grup</item>
</plurals>
<!--PassphraseChangeActivity-->
<string name="PassphraseChangeActivity_passphrases_dont_match_exclamation">Parolalar uyuşmuyor!</string>
@ -1044,7 +1044,7 @@
<string name="WebRtcCallView__start_call">Arama Başlat</string>
<string name="WebRtcCallView__group_call">Grup Araması</string>
<string name="WebRtcCallView__view_participants_list">Katılımcıları Görüntüle</string>
<string name="WebRtcCallView__your_video_is_off">Videonuz Kapalı</string>
<string name="WebRtcCallView__your_video_is_off">Görüntünüz kapalı</string>
<!--CallParticipantsListDialog-->
<plurals name="CallParticipantsListDialog_in_this_call_d_people">
<item quantity="one">Bu aramada · %1$d kişi</item>
@ -1278,7 +1278,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı.</string>
<string name="MediaPreviewActivity_media_delete_confirmation_title">İletiyi sil?</string>
<string name="MediaPreviewActivity_media_delete_confirmation_message">Bu eylem bu iletiyi kalıcı olarak silecektir.</string>
<string name="MediaPreviewActivity_s_to_s">%1$s --&gt; %2$s</string>
<string name="MediaPreviewActivity_media_no_longer_available">Ortam artık mevcut değil.</string>
<string name="MediaPreviewActivity_media_no_longer_available">İçerik artık mevcut değil.</string>
<!--MessageNotifier-->
<string name="MessageNotifier_d_new_messages_in_d_conversations">%2$d konuşmadan %1$d yeni ileti</string>
<string name="MessageNotifier_most_recent_from_s">En yeni ileti: %1$s</string>
@ -1289,7 +1289,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı.</string>
<string name="MessageNotifier_error_delivering_message">İleti tesliminde hata.</string>
<string name="MessageNotifier_mark_all_as_read">Tümünü okundu işaretle</string>
<string name="MessageNotifier_mark_read">Okundu işaretle</string>
<string name="MessageNotifier_turn_off_these_notifications">Bu bildirimleri kapatın</string>
<string name="MessageNotifier_turn_off_these_notifications">Bu bildirimleri kapat</string>
<string name="MessageNotifier_media_message">İçerik iletisi</string>
<string name="MessageNotifier_sticker">Çıkartma</string>
<string name="MessageNotifier_view_once_photo">Tek görümlük görüntü</string>
@ -1734,7 +1734,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı.</string>
<string name="preferences__use_signal_for_viewing_and_storing_all_incoming_multimedia_messages">Gelen tüm içerik iletileri için Signal kullanın</string>
<string name="preferences__pref_enter_sends_title">Enter tuşu ile gönder</string>
<string name="preferences__pressing_the_enter_key_will_send_text_messages">Enter tuşuna basmak metin iletilerini gönderecektir</string>
<string name="preferences__generate_link_previews">Bağlantı ön izlemelerini oluştur</string>
<string name="preferences__generate_link_previews">Bağlantı önizlemelerini oluştur</string>
<string name="preferences__retrieve_link_previews_from_websites_for_messages">Gönderdiğiniz iletiler için doğrudan web sitelerinden bağlantı önizlemelerini al.</string>
<string name="preferences__choose_identity">Kimlik seçin</string>
<string name="preferences__choose_your_contact_entry_from_the_contacts_list">Kişi girdinizi kişi listesinden seçin.</string>

View File

@ -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<GroupChanges.GroupChangeState> 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<GroupChanges.GroupChangeState> changesList = group.getGroupChangesList();
ArrayList<DecryptedGroupHistoryEntry> result = new ArrayList<>(changesList.size());
GroupsV2Operations.GroupOperations groupOperations = groupsOperations.forGroup(groupSecretParams);

View File

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

View File

@ -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<SignalServiceEnvelopeEntity> envelopes;
try {
envelopes = JsonUtil.fromJson(readBodyString(response.body()), SignalServiceEnvelopeEntityList.class).getMessages();
} catch (IOException e) {
throw new PushNetworkException(e);
}
List<SignalServiceEnvelopeEntity> 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> T readBodyJson(ResponseBody body, Class<T> 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> T readResponseJson(Response response, Class<T> 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 = 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<byte[]> 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> contentRange;
public GroupHistory(GroupChanges groupChanges, Optional<ContentRange> 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;

View File

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

View File

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