diff --git a/app/build.gradle b/app/build.gradle index 8021c2201..613b56179 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -80,8 +80,8 @@ protobuf { } } -def canonicalVersionCode = 715 -def canonicalVersionName = "4.72.6" +def canonicalVersionCode = 716 +def canonicalVersionName = "4.73.0" def postFixSize = 10 def abiPostFix = ['universal' : 0, @@ -132,9 +132,10 @@ android { buildConfigField "int", "CONTENT_PROXY_PORT", "443" buildConfigField "String", "SIGNAL_AGENT", "\"OWA\"" buildConfigField "String", "CDS_MRENCLAVE", "\"c98e00a4e3ff977a56afefe7362a27e4961e4f19e211febfbb19b897e6b80b15\"" - buildConfigField "String", "KBS_ENCLAVE_NAME", "\"fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe\"" - buildConfigField "String", "KBS_SERVICE_ID", "\"fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe\"" - buildConfigField "String", "KBS_MRENCLAVE", "\"a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87\"" + buildConfigField "KbsEnclave", "KBS_ENCLAVE", "new KbsEnclave(\"fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe\"," + + "\"fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe\", " + + "\"a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87\")"; + buildConfigField "KbsEnclave[]", "KBS_FALLBACKS", "new KbsEnclave[0]" buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\"" buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X0=\"" buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}' @@ -214,8 +215,10 @@ android { buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api-staging.directory.signal.org\"" buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\"" buildConfigField "String", "CDS_MRENCLAVE", "\"bd123560b01c8fa92935bc5ae15cd2064e5c45215f23f0bd40364d521329d2ad\"" - buildConfigField "String", "KBS_ENCLAVE_NAME", "\"823a3b2c037ff0cbe305cc48928cfcc97c9ed4a8ca6d49af6f7d6981fb60a4e9\"" - buildConfigField "String", "KBS_SERVICE_ID", "\"038c40bbbacdc873caa81ac793bb75afde6dfe436a99ab1f15e3f0cbb7434ced\"" + buildConfigField "KbsEnclave", "KBS_ENCLAVE", "new KbsEnclave(\"823a3b2c037ff0cbe305cc48928cfcc97c9ed4a8ca6d49af6f7d6981fb60a4e9\", " + + "\"038c40bbbacdc873caa81ac793bb75afde6dfe436a99ab1f15e3f0cbb7434ced\", " + + "\"a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87\")" + buildConfigField "KbsEnclave[]", "KBS_FALLBACKS", "new KbsEnclave[0]" buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\"" buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdls=\"" } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4e50e84d1..1221f99d4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -223,6 +223,14 @@ + + + + + + + diff --git a/app/src/main/java/org/thoughtcrime/securesms/KbsEnclave.java b/app/src/main/java/org/thoughtcrime/securesms/KbsEnclave.java new file mode 100644 index 000000000..aa3afa40d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/KbsEnclave.java @@ -0,0 +1,49 @@ +package org.thoughtcrime.securesms; + +import androidx.annotation.NonNull; + +import java.util.Objects; + +/** + * Used in our {@link BuildConfig} to tie together the various attributes of a KBS instance. This + * is sitting in the root directory so it can be accessed by the build config. + */ +public final class KbsEnclave { + + private final String enclaveName; + private final String serviceId; + private final String mrEnclave; + + public KbsEnclave(@NonNull String enclaveName, @NonNull String serviceId, @NonNull String mrEnclave) { + this.enclaveName = enclaveName; + this.serviceId = serviceId; + this.mrEnclave = mrEnclave; + } + + public @NonNull String getMrEnclave() { + return mrEnclave; + } + + public @NonNull String getEnclaveName() { + return enclaveName; + } + + public @NonNull String getServiceId() { + return serviceId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + KbsEnclave enclave = (KbsEnclave) o; + return enclaveName.equals(enclave.enclaveName) && + serviceId.equals(enclave.serviceId) && + mrEnclave.equals(enclave.mrEnclave); + } + + @Override + public int hashCode() { + return Objects.hash(enclaveName, serviceId, mrEnclave); + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 8ea5d57ab..ccc55393e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -815,10 +815,9 @@ public class ConversationActivity extends PassphraseRequiredActivity } else { menu.findItem(R.id.menu_distribution_conversation).setChecked(true); } - inflater.inflate(R.menu.conversation_active_group_options, menu); - } else if (isActiveV2Group || isActiveGroup) { - inflater.inflate(R.menu.conversation_active_group_options, menu); } + + inflater.inflate(R.menu.conversation_active_group_options, menu); } inflater.inflate(R.menu.conversation, menu); @@ -865,7 +864,7 @@ public class ConversationActivity extends PassphraseRequiredActivity if (isActiveV2Group) { hideMenuItem(menu, R.id.menu_mute_notifications); hideMenuItem(menu, R.id.menu_conversation_settings); - } else if (isActiveGroup) { + } else if (isGroupConversation()) { hideMenuItem(menu, R.id.menu_conversation_settings); } @@ -2227,6 +2226,10 @@ public class ConversationActivity extends PassphraseRequiredActivity private Drafts getDraftsForCurrentState() { Drafts drafts = new Drafts(); + if (recipient.get().isGroup() && !recipient.get().isActiveGroup()) { + return drafts; + } + if (!Util.isEmpty(composeText)) { drafts.add(new Draft(Draft.TEXT, composeText.getTextTrimmed().toString())); List draftMentions = composeText.getMentions(); @@ -3036,7 +3039,7 @@ public class ConversationActivity extends PassphraseRequiredActivity messageRequestBottomView.setBlockOnClickListener(v -> onMessageRequestBlockClicked(viewModel)); messageRequestBottomView.setUnblockOnClickListener(v -> onMessageRequestUnblockClicked(viewModel)); - viewModel.getRecipient().observe(this, this::presentMessageRequestBottomViewTo); + viewModel.getMessageData().observe(this, this::presentMessageRequestBottomViewTo); viewModel.getMessageRequestDisplayState().observe(this, this::presentMessageRequestDisplayState); viewModel.getFailures().observe(this, this::showGroupChangeErrorToast); viewModel.getMessageRequestStatus().observe(this, status -> { @@ -3081,7 +3084,7 @@ public class ConversationActivity extends PassphraseRequiredActivity { reactionOverlay.setOnToolbarItemClickedListener(toolbarListener); reactionOverlay.setOnHideListener(onHideListener); - reactionOverlay.show(this, maskTarget, messageRecord, inputAreaHeight()); + reactionOverlay.show(this, maskTarget, recipient.get(), messageRecord, inputAreaHeight()); } @Override @@ -3449,10 +3452,10 @@ public class ConversationActivity extends PassphraseRequiredActivity } } - private void presentMessageRequestBottomViewTo(@Nullable Recipient recipient) { - if (recipient == null) return; + private void presentMessageRequestBottomViewTo(@Nullable MessageRequestViewModel.MessageData messageData) { + if (messageData == null) return; - messageRequestBottomView.setRecipient(recipient); + messageRequestBottomView.setMessageData(messageData); } private static class KeyboardImageDetails { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index 6c710cdc9..7616175c1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -226,7 +226,10 @@ public class ConversationFragment extends LoggingFragment { new ConversationItemSwipeCallback( conversationMessage -> actionMode == null && - MenuState.canReplyToMessage(MenuState.isActionMessage(conversationMessage.getMessageRecord()), conversationMessage.getMessageRecord(), messageRequestViewModel.shouldShowMessageRequest()), + MenuState.canReplyToMessage(recipient.get(), + MenuState.isActionMessage(conversationMessage.getMessageRecord()), + conversationMessage.getMessageRecord(), + messageRequestViewModel.shouldShowMessageRequest()), this::handleReplyMessage ).attachToRecyclerView(list); @@ -573,7 +576,7 @@ public class ConversationFragment extends LoggingFragment { return; } - MenuState menuState = MenuState.getMenuState(Stream.of(messages).map(ConversationMessage::getMessageRecord).collect(Collectors.toSet()), messageRequestViewModel.shouldShowMessageRequest()); + MenuState menuState = MenuState.getMenuState(recipient.get(), Stream.of(messages).map(ConversationMessage::getMessageRecord).collect(Collectors.toSet()), messageRequestViewModel.shouldShowMessageRequest()); menu.findItem(R.id.menu_context_forward).setVisible(menuState.shouldShowForwardAction()); menu.findItem(R.id.menu_context_reply).setVisible(menuState.shouldShowReplyAction()); @@ -662,53 +665,7 @@ public class ConversationFragment extends LoggingFragment { private void handleDeleteMessages(final Set conversationMessages) { Set messageRecords = Stream.of(conversationMessages).map(ConversationMessage::getMessageRecord).collect(Collectors.toSet()); - if (FeatureFlags.remoteDelete()) { - buildRemoteDeleteConfirmationDialog(messageRecords).show(); - } else { - buildLegacyDeleteConfirmationDialog(messageRecords).show(); - } - } - - private AlertDialog.Builder buildLegacyDeleteConfirmationDialog(Set messageRecords) { - int messagesCount = messageRecords.size(); - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - - builder.setIconAttribute(R.attr.dialog_alert_icon); - builder.setTitle(getActivity().getResources().getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messagesCount, messagesCount)); - builder.setMessage(getActivity().getResources().getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messagesCount, messagesCount)); - builder.setCancelable(true); - - builder.setPositiveButton(R.string.delete, (dialog, which) -> { - new ProgressDialogAsyncTask(getActivity(), - R.string.ConversationFragment_deleting, - R.string.ConversationFragment_deleting_messages) - { - @Override - protected Void doInBackground(Void... voids) { - for (MessageRecord messageRecord : messageRecords) { - boolean threadDeleted; - - if (messageRecord.isMms()) { - threadDeleted = DatabaseFactory.getMmsDatabase(getActivity()).deleteMessage(messageRecord.getId()); - } else { - threadDeleted = DatabaseFactory.getSmsDatabase(getActivity()).deleteMessage(messageRecord.getId()); - } - - if (threadDeleted) { - threadId = -1; - conversationViewModel.clearThreadId(); - messageCountsViewModel.clearThreadId(); - listener.setThreadId(threadId); - } - } - - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - }); - - builder.setNegativeButton(android.R.string.cancel, null); - return builder; + buildRemoteDeleteConfirmationDialog(messageRecords).show(); } private AlertDialog.Builder buildRemoteDeleteConfirmationDialog(Set messageRecords) { @@ -769,7 +726,7 @@ public class ConversationFragment extends LoggingFragment { deleteForEveryone.run(); } else { new AlertDialog.Builder(requireActivity()) - .setMessage(R.string.ConversationFragment_this_message_will_be_permanently_deleted_for_everyone) + .setMessage(R.string.ConversationFragment_this_message_will_be_deleted_for_everyone_in_the_conversation) .setPositiveButton(R.string.ConversationFragment_delete_for_everyone, (dialog, which) -> { SignalStore.uiHints().markHasConfirmedDeleteForEveryoneOnce(); deleteForEveryone.run(); @@ -1223,11 +1180,12 @@ public class ConversationFragment extends LoggingFragment { MessageRecord messageRecord = conversationMessage.getMessageRecord(); - if (messageRecord.isSecure() && - !messageRecord.isRemoteDelete() && - !messageRecord.isUpdate() && - !recipient.get().isBlocked() && - !messageRequestViewModel.shouldShowMessageRequest() && + if (messageRecord.isSecure() && + !messageRecord.isRemoteDelete() && + !messageRecord.isUpdate() && + !recipient.get().isBlocked() && + !messageRequestViewModel.shouldShowMessageRequest() && + (!recipient.get().isGroup() || recipient.get().isActiveGroup()) && ((ConversationAdapter) list.getAdapter()).getSelectedItems().isEmpty()) { isReacting = true; diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java index 6137cb57c..6aaf0b7ff 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java @@ -5,9 +5,7 @@ import android.animation.AnimatorSet; import android.app.Activity; import android.content.Context; import android.graphics.PointF; -import android.graphics.PorterDuff; import android.graphics.Rect; -import android.graphics.drawable.Drawable; import android.os.Build; import android.util.AttributeSet; import android.view.HapticFeedbackConstants; @@ -24,7 +22,6 @@ import androidx.annotation.Nullable; import androidx.appcompat.widget.Toolbar; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintSet; -import androidx.core.content.ContextCompat; import androidx.vectordrawable.graphics.drawable.AnimatorInflaterCompat; import com.annimon.stream.Stream; @@ -60,6 +57,7 @@ public final class ConversationReactionOverlay extends RelativeLayout { private final PointF lastSeenDownPoint = new PointF(); private Activity activity; + private Recipient conversationRecipient; private MessageRecord messageRecord; private OverlayState overlayState = OverlayState.HIDDEN; @@ -145,15 +143,21 @@ public final class ConversationReactionOverlay extends RelativeLayout { maskView.setTargetParentTranslationY(translationY); } - public void show(@NonNull Activity activity, @NonNull View maskTarget, @NonNull MessageRecord messageRecord, int maskPaddingBottom) { + public void show(@NonNull Activity activity, + @NonNull View maskTarget, + @NonNull Recipient conversationRecipient, + @NonNull MessageRecord messageRecord, + int maskPaddingBottom) + { if (overlayState != OverlayState.HIDDEN) { return; } - this.messageRecord = messageRecord; - overlayState = OverlayState.UNINITAILIZED; - selected = -1; + this.messageRecord = messageRecord; + this.conversationRecipient = conversationRecipient; + overlayState = OverlayState.UNINITAILIZED; + selected = -1; setupToolbarMenuItems(); setupSelectedEmoji(); @@ -498,7 +502,7 @@ public final class ConversationReactionOverlay extends RelativeLayout { } private void setupToolbarMenuItems() { - MenuState menuState = MenuState.getMenuState(Collections.singleton(messageRecord), false); + MenuState menuState = MenuState.getMenuState(conversationRecipient, Collections.singleton(messageRecord), false); toolbar.getMenu().findItem(R.id.action_copy).setVisible(menuState.shouldShowCopyAction()); toolbar.getMenu().findItem(R.id.action_download).setVisible(menuState.shouldShowSaveAttachmentAction()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/MenuState.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/MenuState.java index ad703d7bd..22d3f1c83 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/MenuState.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/MenuState.java @@ -5,6 +5,7 @@ import androidx.annotation.NonNull; import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord; +import org.thoughtcrime.securesms.recipients.Recipient; import java.util.Set; @@ -50,7 +51,8 @@ final class MenuState { return copy; } - static MenuState getMenuState(@NonNull Set messageRecords, + static MenuState getMenuState(@NonNull Recipient conversationRecipient, + @NonNull Set messageRecords, boolean shouldShowMessageRequest) { @@ -102,20 +104,21 @@ final class MenuState { ((MediaMmsMessageRecord)messageRecord).getSlideDeck().getStickerSlide() == null) .shouldShowForwardAction(!actionMessage && !sharedContact && !viewOnce && !remoteDelete) .shouldShowDetailsAction(!actionMessage) - .shouldShowReplyAction(canReplyToMessage(actionMessage, messageRecord, shouldShowMessageRequest)); + .shouldShowReplyAction(canReplyToMessage(conversationRecipient, actionMessage, messageRecord, shouldShowMessageRequest)); } return builder.shouldShowCopyAction(!actionMessage && !remoteDelete && hasText) .build(); } - static boolean canReplyToMessage(boolean actionMessage, @NonNull MessageRecord messageRecord, boolean isDisplayingMessageRequest) { - return !actionMessage && - !messageRecord.isRemoteDelete() && - !messageRecord.isPending() && - !messageRecord.isFailed() && - !isDisplayingMessageRequest && - messageRecord.isSecure() && + static boolean canReplyToMessage(@NonNull Recipient conversationRecipient, boolean actionMessage, @NonNull MessageRecord messageRecord, boolean isDisplayingMessageRequest) { + return !actionMessage && + !messageRecord.isRemoteDelete() && + !messageRecord.isPending() && + !messageRecord.isFailed() && + !isDisplayingMessageRequest && + messageRecord.isSecure() && + (!conversationRecipient.isGroup() || conversationRecipient.isActiveGroup()) && !messageRecord.getRecipient().isBlocked(); } 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 253d78f68..1dbf48096 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -15,7 +15,6 @@ import net.sqlcipher.database.SQLiteConstraintException; import net.sqlcipher.database.SQLiteDatabase; import org.signal.storageservice.protos.groups.local.DecryptedGroup; -import org.signal.zkgroup.InvalidInputException; import org.signal.zkgroup.groups.GroupMasterKey; import org.signal.zkgroup.profiles.ProfileKey; import org.signal.zkgroup.profiles.ProfileKeyCredential; @@ -23,6 +22,7 @@ import org.thoughtcrime.securesms.color.MaterialColor; import org.thoughtcrime.securesms.contacts.avatars.ContactColors; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord; +import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; @@ -41,6 +41,7 @@ import org.thoughtcrime.securesms.storage.StorageSyncHelper.RecordUpdate; import org.thoughtcrime.securesms.storage.StorageSyncModels; import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.CursorUtil; +import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.IdentityUtil; import org.thoughtcrime.securesms.util.SqlUtil; import org.thoughtcrime.securesms.util.StringUtil; @@ -61,6 +62,7 @@ import org.whispersystems.signalservice.api.util.UuidUtil; import java.io.Closeable; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -128,7 +130,7 @@ public class RecipientDatabase extends Database { private static final String IDENTITY_KEY = "identity_key"; private static final String[] RECIPIENT_PROJECTION = new String[] { - UUID, USERNAME, PHONE, EMAIL, GROUP_ID, GROUP_TYPE, + ID, UUID, USERNAME, PHONE, EMAIL, GROUP_ID, GROUP_TYPE, BLOCKED, MESSAGE_RINGTONE, CALL_RINGTONE, MESSAGE_VIBRATE, CALL_VIBRATE, MUTE_UNTIL, COLOR, SEEN_INVITE_REMINDER, DEFAULT_SUBSCRIPTION_ID, MESSAGE_EXPIRATION_TIME, REGISTERED, PROFILE_KEY, PROFILE_KEY_CREDENTIAL, SYSTEM_DISPLAY_NAME, SYSTEM_PHOTO_URI, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, SYSTEM_CONTACT_URI, @@ -144,20 +146,13 @@ public class RecipientDatabase extends Database { private static final String[] ID_PROJECTION = new String[]{ID}; private static final String[] SEARCH_PROJECTION = new String[]{ID, SYSTEM_DISPLAY_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, "COALESCE(" + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ") AS " + SEARCH_PROFILE_NAME, "COALESCE(" + nullIfEmpty(SYSTEM_DISPLAY_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ") AS " + SORT_NAME}; public static final String[] SEARCH_PROJECTION_NAMES = new String[]{ID, SYSTEM_DISPLAY_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, SEARCH_PROFILE_NAME, SORT_NAME}; - static final String[] TYPED_RECIPIENT_PROJECTION = Stream.of(RECIPIENT_PROJECTION) + private static final String[] TYPED_RECIPIENT_PROJECTION = Stream.of(RECIPIENT_PROJECTION) .map(columnName -> TABLE_NAME + "." + columnName) .toList().toArray(new String[0]); - private static final String[] MENTION_SEARCH_PROJECTION = new String[]{ID, removeWhitespace("COALESCE(" + nullIfEmpty(SYSTEM_DISPLAY_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ", " + nullIfEmpty(PHONE) + ")") + " AS " + SORT_NAME}; + static final String[] TYPED_RECIPIENT_PROJECTION_NO_ID = Arrays.copyOfRange(TYPED_RECIPIENT_PROJECTION, 1, TYPED_RECIPIENT_PROJECTION.length); - private static final String[] RECIPIENT_FULL_PROJECTION = Stream.of( - new String[] { TABLE_NAME + "." + ID, - TABLE_NAME + "." + STORAGE_PROTO }, - TYPED_RECIPIENT_PROJECTION, - new String[] { - IdentityDatabase.TABLE_NAME + "." + IdentityDatabase.VERIFIED + " AS " + IDENTITY_STATUS, - IdentityDatabase.TABLE_NAME + "." + IdentityDatabase.IDENTITY_KEY + " AS " + IDENTITY_KEY - }).flatMap(Stream::of).toArray(String[]::new); + private static final String[] MENTION_SEARCH_PROJECTION = new String[]{ID, removeWhitespace("COALESCE(" + nullIfEmpty(SYSTEM_DISPLAY_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ", " + nullIfEmpty(PHONE) + ")") + " AS " + SORT_NAME}; public static final String[] CREATE_INDEXS = new String[] { "CREATE INDEX IF NOT EXISTS recipient_dirty_index ON " + TABLE_NAME + " (" + DIRTY + ");", @@ -595,11 +590,10 @@ public class RecipientDatabase extends Database { public @NonNull RecipientSettings getRecipientSettings(@NonNull RecipientId id) { SQLiteDatabase database = databaseHelper.getReadableDatabase(); - String table = TABLE_NAME + " LEFT OUTER JOIN " + IdentityDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + ID + " = " + IdentityDatabase.TABLE_NAME + "." + IdentityDatabase.RECIPIENT_ID; - String query = TABLE_NAME + "." + ID + " = ?"; + String query = ID + " = ?"; String[] args = new String[] { id.serialize() }; - try (Cursor cursor = database.query(table, RECIPIENT_FULL_PROJECTION, query, args, null, null, null)) { + try (Cursor cursor = database.query(TABLE_NAME, RECIPIENT_PROJECTION, query, args, null, null, null)) { if (cursor != null && cursor.moveToNext()) { return getRecipientSettings(context, cursor); } else { @@ -674,6 +668,20 @@ public class RecipientDatabase extends Database { return null; } + public void markNeedsSync(@NonNull Collection recipientIds) { + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + + db.beginTransaction(); + try { + for (RecipientId recipientId : recipientIds) { + markDirty(recipientId, DirtyState.UPDATE); + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + public void markNeedsSync(@NonNull RecipientId recipientId) { markDirty(recipientId, DirtyState.UPDATE); } @@ -696,6 +704,10 @@ public class RecipientDatabase extends Database { } finally { db.endTransaction(); } + + for (RecipientId id : storageIds.keySet()) { + Recipient.live(id).refresh(); + } } public void applyStorageSyncUpdates(@NonNull Collection contactInserts, @@ -770,7 +782,7 @@ public class RecipientDatabase extends Database { } } - threadDatabase.setArchived(recipientId, insert.isArchived()); + threadDatabase.applyStorageSyncUpdate(recipientId, insert); needsRefresh.add(recipientId); } @@ -810,12 +822,12 @@ public class RecipientDatabase extends Database { Optional newIdentityRecord = identityDatabase.getIdentity(recipientId); - if ((newIdentityRecord.isPresent() && newIdentityRecord.get().getVerifiedStatus() == IdentityDatabase.VerifiedStatus.VERIFIED) && - (!oldIdentityRecord.isPresent() || oldIdentityRecord.get().getVerifiedStatus() != IdentityDatabase.VerifiedStatus.VERIFIED)) + if ((newIdentityRecord.isPresent() && newIdentityRecord.get().getVerifiedStatus() == VerifiedStatus.VERIFIED) && + (!oldIdentityRecord.isPresent() || oldIdentityRecord.get().getVerifiedStatus() != VerifiedStatus.VERIFIED)) { IdentityUtil.markIdentityVerified(context, Recipient.resolved(recipientId), true, true); - } else if ((newIdentityRecord.isPresent() && newIdentityRecord.get().getVerifiedStatus() != IdentityDatabase.VerifiedStatus.VERIFIED) && - (oldIdentityRecord.isPresent() && oldIdentityRecord.get().getVerifiedStatus() == IdentityDatabase.VerifiedStatus.VERIFIED)) + } else if ((newIdentityRecord.isPresent() && newIdentityRecord.get().getVerifiedStatus() != VerifiedStatus.VERIFIED) && + (oldIdentityRecord.isPresent() && oldIdentityRecord.get().getVerifiedStatus() == VerifiedStatus.VERIFIED)) { IdentityUtil.markIdentityVerified(context, Recipient.resolved(recipientId), false, true); } @@ -823,7 +835,7 @@ public class RecipientDatabase extends Database { Log.w(TAG, "Failed to process identity key during update! Skipping.", e); } - threadDatabase.setArchived(recipientId, update.getNew().isArchived()); + threadDatabase.applyStorageSyncUpdate(recipientId, update.getNew()); needsRefresh.add(recipientId); } @@ -832,7 +844,7 @@ public class RecipientDatabase extends Database { Recipient recipient = Recipient.externalGroup(context, GroupId.v1orThrow(insert.getGroupId())); - threadDatabase.setArchived(recipient.getId(), insert.isArchived()); + threadDatabase.applyStorageSyncUpdate(recipient.getId(), insert); needsRefresh.add(recipient.getId()); } @@ -846,7 +858,7 @@ public class RecipientDatabase extends Database { Recipient recipient = Recipient.externalGroup(context, GroupId.v1orThrow(update.getOld().getGroupId())); - threadDatabase.setArchived(recipient.getId(), update.getNew().isArchived()); + threadDatabase.applyStorageSyncUpdate(recipient.getId(), update.getNew()); needsRefresh.add(recipient.getId()); } @@ -874,7 +886,7 @@ public class RecipientDatabase extends Database { ApplicationDependencies.getJobManager().add(new RequestGroupV2InfoJob(groupId)); - threadDatabase.setArchived(recipient.getId(), insert.isArchived()); + threadDatabase.applyStorageSyncUpdate(recipient.getId(), insert); needsRefresh.add(recipient.getId()); } @@ -889,7 +901,7 @@ public class RecipientDatabase extends Database { GroupMasterKey masterKey = update.getOld().getMasterKeyOrThrow(); Recipient recipient = Recipient.externalGroup(context, GroupId.v2(masterKey)); - threadDatabase.setArchived(recipient.getId(), update.getNew().isArchived()); + threadDatabase.applyStorageSyncUpdate(recipient.getId(), update.getNew()); needsRefresh.add(recipient.getId()); } @@ -938,6 +950,8 @@ public class RecipientDatabase extends Database { ApplicationDependencies.getJobManager().add(new RefreshAttributesJob()); } + DatabaseFactory.getThreadDatabase(context).applyStorageSyncUpdate(Recipient.self().getId(), update); + Recipient.self().live().refresh(); } @@ -1050,12 +1064,20 @@ public class RecipientDatabase extends Database { private List getRecipientSettingsForSync(@Nullable String query, @Nullable String[] args) { SQLiteDatabase db = databaseHelper.getReadableDatabase(); - String table = TABLE_NAME + " LEFT OUTER JOIN " + IdentityDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + ID + " = " + IdentityDatabase.TABLE_NAME + "." + IdentityDatabase.RECIPIENT_ID - + " LEFT OUTER JOIN " + GroupDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + GROUP_ID + " = " + GroupDatabase.TABLE_NAME + "." + GroupDatabase.GROUP_ID; + String table = TABLE_NAME + " LEFT OUTER JOIN " + IdentityDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + ID + " = " + IdentityDatabase.TABLE_NAME + "." + IdentityDatabase.RECIPIENT_ID + + " LEFT OUTER JOIN " + GroupDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + GROUP_ID + " = " + GroupDatabase.TABLE_NAME + "." + GroupDatabase.GROUP_ID + + " LEFT OUTER JOIN " + ThreadDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + ID + " = " + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.RECIPIENT_ID; List out = new ArrayList<>(); - String[] columns = Stream.of(RECIPIENT_FULL_PROJECTION, - new String[]{GroupDatabase.TABLE_NAME + "." + GroupDatabase.V2_MASTER_KEY }).flatMap(Stream::of).toArray(String[]::new); + String[] columns = Stream.of(TYPED_RECIPIENT_PROJECTION, + new String[]{ RecipientDatabase.TABLE_NAME + "." + STORAGE_PROTO, + GroupDatabase.TABLE_NAME + "." + GroupDatabase.V2_MASTER_KEY, + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ARCHIVED, + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.READ, + IdentityDatabase.TABLE_NAME + "." + IdentityDatabase.VERIFIED + " AS " + IDENTITY_STATUS, + IdentityDatabase.TABLE_NAME + "." + IdentityDatabase.IDENTITY_KEY + " AS " + IDENTITY_KEY }) + .flatMap(Stream::of) + .toArray(String[]::new); try (Cursor cursor = db.query(table, columns, query, args, null, null, null)) { while (cursor != null && cursor.moveToNext()) { @@ -1155,23 +1177,6 @@ public class RecipientDatabase extends Database { int groupsV2CapabilityValue = CursorUtil.requireInt(cursor, GROUPS_V2_CAPABILITY); String storageKeyRaw = CursorUtil.requireString(cursor, STORAGE_SERVICE_ID); int mentionSettingId = CursorUtil.requireInt(cursor, MENTION_SETTING); - String storageProtoRaw = CursorUtil.getString(cursor, STORAGE_PROTO).orNull(); - - Optional identityKeyRaw = CursorUtil.getString(cursor, IDENTITY_KEY); - Optional identityStatusRaw = CursorUtil.getInt(cursor, IDENTITY_STATUS); - - int masterKeyIndex = cursor.getColumnIndex(GroupDatabase.V2_MASTER_KEY); - GroupMasterKey groupMasterKey = null; - try { - if (masterKeyIndex != -1) { - byte[] blob = cursor.getBlob(masterKeyIndex); - if (blob != null) { - groupMasterKey = new GroupMasterKey(blob); - } - } - } catch (InvalidInputException e) { - throw new AssertionError(e); - } MaterialColor color; byte[] profileKey = null; @@ -1202,30 +1207,58 @@ public class RecipientDatabase extends Database { } } - byte[] storageKey = storageKeyRaw != null ? Base64.decodeOrThrow(storageKeyRaw) : null; - byte[] identityKey = identityKeyRaw.transform(Base64::decodeOrThrow).orNull(); - byte[] storageProto = storageProtoRaw != null ? Base64.decodeOrThrow(storageProtoRaw) : null; + byte[] storageKey = storageKeyRaw != null ? Base64.decodeOrThrow(storageKeyRaw) : null; - IdentityDatabase.VerifiedStatus identityStatus = identityStatusRaw.transform(IdentityDatabase.VerifiedStatus::forState).or(IdentityDatabase.VerifiedStatus.DEFAULT); - - return new RecipientSettings(RecipientId.from(id), uuid, username, e164, email, groupId, groupMasterKey, GroupType.fromId(groupType), blocked, muteUntil, + return new RecipientSettings(RecipientId.from(id), + uuid, + username, + e164, + email, + groupId, + GroupType.fromId(groupType), + blocked, + muteUntil, VibrateState.fromId(messageVibrateState), VibrateState.fromId(callVibrateState), - Util.uri(messageRingtone), Util.uri(callRingtone), - color, defaultSubscriptionId, expireMessages, + Util.uri(messageRingtone), + Util.uri(callRingtone), + color, + defaultSubscriptionId, + expireMessages, RegisteredState.fromId(registeredState), - profileKey, profileKeyCredential, - systemDisplayName, systemContactPhoto, - systemPhoneLabel, systemContactUri, - ProfileName.fromParts(profileGivenName, profileFamilyName), signalProfileAvatar, - AvatarHelper.hasAvatar(context, RecipientId.from(id)), profileSharing, lastProfileFetch, - notificationChannel, UnidentifiedAccessMode.fromMode(unidentifiedAccessMode), + profileKey, + profileKeyCredential, + systemDisplayName, + systemContactPhoto, + systemPhoneLabel, + systemContactUri, + ProfileName.fromParts(profileGivenName, profileFamilyName), + signalProfileAvatar, + AvatarHelper.hasAvatar(context, RecipientId.from(id)), + profileSharing, + lastProfileFetch, + notificationChannel, + UnidentifiedAccessMode.fromMode(unidentifiedAccessMode), forceSmsSelection, Recipient.Capability.deserialize(uuidCapabilityValue), Recipient.Capability.deserialize(groupsV2CapabilityValue), InsightsBannerTier.fromId(insightsBannerTier), - storageKey, identityKey, identityStatus, MentionSetting.fromId(mentionSettingId), - storageProto); + storageKey, + MentionSetting.fromId(mentionSettingId), + getSyncExtras(cursor)); + } + + private static @NonNull RecipientSettings.SyncExtras getSyncExtras(@NonNull Cursor cursor) { + String storageProtoRaw = CursorUtil.getString(cursor, STORAGE_PROTO).orNull(); + byte[] storageProto = storageProtoRaw != null ? Base64.decodeOrThrow(storageProtoRaw) : null; + boolean archived = CursorUtil.getBoolean(cursor, ThreadDatabase.ARCHIVED).or(false); + boolean forcedUnread = CursorUtil.getInt(cursor, ThreadDatabase.READ).transform(status -> status == ThreadDatabase.ReadStatus.FORCED_UNREAD.serialize()).or(false); + GroupMasterKey groupMasterKey = CursorUtil.getBlob(cursor, GroupDatabase.V2_MASTER_KEY).transform(GroupUtil::requireMasterKey).orNull(); + byte[] identityKey = CursorUtil.getString(cursor, IDENTITY_KEY).transform(Base64::decodeOrThrow).orNull(); + VerifiedStatus identityStatus = CursorUtil.getInt(cursor, IDENTITY_STATUS).transform(VerifiedStatus::forState).or(VerifiedStatus.DEFAULT); + + + return new RecipientSettings.SyncExtras(storageProto, groupMasterKey, identityKey, identityStatus, archived, forcedUnread); } public BulkOperationsHandle beginBulkSystemContactUpdate() { @@ -1406,7 +1439,7 @@ public class RecipientDatabase extends Database { valuesToSet.putNull(PROFILE_KEY_CREDENTIAL); valuesToSet.put(UNIDENTIFIED_ACCESS_MODE, UnidentifiedAccessMode.UNKNOWN.getMode()); - SqlUtil.UpdateQuery updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, valuesToCompare); + SqlUtil.Query updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, valuesToCompare); if (update(updateQuery, valuesToSet)) { markDirty(id, DirtyState.UPDATE); @@ -1455,7 +1488,7 @@ public class RecipientDatabase extends Database { values.put(PROFILE_KEY_CREDENTIAL, Base64.encodeBytes(profileKeyCredential.serialize())); - SqlUtil.UpdateQuery updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, values); + SqlUtil.Query updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, values); if (update(updateQuery, values)) { // TODO [greyson] If we sync this in future, mark dirty @@ -2172,6 +2205,10 @@ public class RecipientDatabase extends Database { } finally { db.endTransaction(); } + + for (RecipientId id : keys.keySet()) { + Recipient.live(id).refresh(); + } } public void clearDirtyState(@NonNull List recipients) { @@ -2230,9 +2267,7 @@ public class RecipientDatabase extends Database { * query such that this will only return true if a row was *actually* updated. */ private boolean update(@NonNull RecipientId id, @NonNull ContentValues contentValues) { - String selection = ID + " = ?"; - String[] args = new String[]{id.serialize()}; - SqlUtil.UpdateQuery updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, contentValues); + SqlUtil.Query updateQuery = SqlUtil.buildTrueUpdateQuery(ID_WHERE, SqlUtil.buildArgs(id), contentValues); return update(updateQuery, contentValues); } @@ -2242,7 +2277,7 @@ public class RecipientDatabase extends Database { *

* This will only return true if a row was *actually* updated with respect to the where clause of the {@param updateQuery}. */ - private boolean update(@NonNull SqlUtil.UpdateQuery updateQuery, @NonNull ContentValues contentValues) { + private boolean update(@NonNull SqlUtil.Query updateQuery, @NonNull ContentValues contentValues) { SQLiteDatabase database = databaseHelper.getWritableDatabase(); return database.update(TABLE_NAME, contentValues, updateQuery.getWhere(), updateQuery.getWhereArgs()) > 0; @@ -2529,7 +2564,6 @@ public class RecipientDatabase extends Database { private final String e164; private final String email; private final GroupId groupId; - private final GroupMasterKey groupMasterKey; private final GroupType groupType; private final boolean blocked; private final long muteUntil; @@ -2559,10 +2593,8 @@ public class RecipientDatabase extends Database { private final Recipient.Capability groupsV2Capability; private final InsightsBannerTier insightsBannerTier; private final byte[] storageId; - private final byte[] identityKey; - private final IdentityDatabase.VerifiedStatus identityStatus; private final MentionSetting mentionSetting; - private final byte[] storageProto; + private final SyncExtras syncExtras; RecipientSettings(@NonNull RecipientId id, @Nullable UUID uuid, @@ -2570,7 +2602,6 @@ public class RecipientDatabase extends Database { @Nullable String e164, @Nullable String email, @Nullable GroupId groupId, - @Nullable GroupMasterKey groupMasterKey, @NonNull GroupType groupType, boolean blocked, long muteUntil, @@ -2600,10 +2631,8 @@ public class RecipientDatabase extends Database { Recipient.Capability groupsV2Capability, @NonNull InsightsBannerTier insightsBannerTier, @Nullable byte[] storageId, - @Nullable byte[] identityKey, - @NonNull IdentityDatabase.VerifiedStatus identityStatus, @NonNull MentionSetting mentionSetting, - @Nullable byte[] storageProto) + @NonNull SyncExtras syncExtras) { this.id = id; this.uuid = uuid; @@ -2611,7 +2640,6 @@ public class RecipientDatabase extends Database { this.e164 = e164; this.email = email; this.groupId = groupId; - this.groupMasterKey = groupMasterKey; this.groupType = groupType; this.blocked = blocked; this.muteUntil = muteUntil; @@ -2641,10 +2669,8 @@ public class RecipientDatabase extends Database { this.groupsV2Capability = groupsV2Capability; this.insightsBannerTier = insightsBannerTier; this.storageId = storageId; - this.identityKey = identityKey; - this.identityStatus = identityStatus; this.mentionSetting = mentionSetting; - this.storageProto = storageProto; + this.syncExtras = syncExtras; } public RecipientId getId() { @@ -2671,13 +2697,6 @@ public class RecipientDatabase extends Database { return groupId; } - /** - * Only read populated for sync. - */ - public @Nullable GroupMasterKey getGroupMasterKey() { - return groupMasterKey; - } - public @NonNull GroupType getGroupType() { return groupType; } @@ -2794,20 +2813,64 @@ public class RecipientDatabase extends Database { return storageId; } - public @Nullable byte[] getIdentityKey() { - return identityKey; - } - - public @NonNull IdentityDatabase.VerifiedStatus getIdentityStatus() { - return identityStatus; - } - public @NonNull MentionSetting getMentionSetting() { return mentionSetting; } - public @Nullable byte[] getStorageProto() { - return storageProto; + public @NonNull SyncExtras getSyncExtras() { + return syncExtras; + } + + /** + * A bundle of data that's only necessary when syncing to storage service, not for a + * {@link Recipient}. + */ + public static class SyncExtras { + private final byte[] storageProto; + private final GroupMasterKey groupMasterKey; + private final byte[] identityKey; + private final VerifiedStatus identityStatus; + private final boolean archived; + private final boolean forcedUnread; + + public SyncExtras(@Nullable byte[] storageProto, + @Nullable GroupMasterKey groupMasterKey, + @Nullable byte[] identityKey, + @NonNull VerifiedStatus identityStatus, + boolean archived, + boolean forcedUnread) + { + this.storageProto = storageProto; + this.groupMasterKey = groupMasterKey; + this.identityKey = identityKey; + this.identityStatus = identityStatus; + this.archived = archived; + this.forcedUnread = forcedUnread; + } + + public @Nullable byte[] getStorageProto() { + return storageProto; + } + + public @Nullable GroupMasterKey getGroupMasterKey() { + return groupMasterKey; + } + + public boolean isArchived() { + return archived; + } + + public @Nullable byte[] getIdentityKey() { + return identityKey; + } + + public @NonNull VerifiedStatus getIdentityStatus() { + return identityStatus; + } + + public boolean isForcedUnread() { + return forcedUnread; + } } } 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 d19c3f810..911c97de0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -32,8 +32,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import net.sqlcipher.database.SQLiteDatabase; import org.jsoup.helper.StringUtil; -import org.signal.storageservice.protos.groups.local.DecryptedGroup; -import org.signal.storageservice.protos.groups.local.DecryptedMember; import org.thoughtcrime.securesms.database.MessageDatabase.MarkedMessageInfo; import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; @@ -56,11 +54,15 @@ import org.thoughtcrime.securesms.util.SqlUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil; -import org.whispersystems.signalservice.api.util.UuidUtil; +import org.whispersystems.signalservice.api.storage.SignalAccountRecord; +import org.whispersystems.signalservice.api.storage.SignalContactRecord; +import org.whispersystems.signalservice.api.storage.SignalGroupV1Record; +import org.whispersystems.signalservice.api.storage.SignalGroupV2Record; +import org.whispersystems.signalservice.api.storage.SignalStorageRecord; import java.io.Closeable; import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -70,7 +72,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.UUID; public class ThreadDatabase extends Database { @@ -102,7 +103,7 @@ public class ThreadDatabase extends Database { public static final String LAST_SEEN = "last_seen"; public static final String HAS_SENT = "has_sent"; private static final String LAST_SCROLLED = "last_scrolled"; - private static final String PINNED = "pinned"; + static final String PINNED = "pinned"; public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " + DATE + " INTEGER DEFAULT 0, " + @@ -144,7 +145,7 @@ public class ThreadDatabase extends Database { .toList(); private static final List COMBINED_THREAD_RECIPIENT_GROUP_PROJECTION = Stream.concat(Stream.concat(Stream.of(TYPED_THREAD_PROJECTION), - Stream.of(RecipientDatabase.TYPED_RECIPIENT_PROJECTION)), + Stream.of(RecipientDatabase.TYPED_RECIPIENT_PROJECTION_NO_ID)), Stream.of(GroupDatabase.TYPED_GROUP_PROJECTION)) .toList(); @@ -405,6 +406,7 @@ public class ThreadDatabase extends Database { List smsRecords = new LinkedList<>(); List mmsRecords = new LinkedList<>(); + boolean needsSync = false; db.beginTransaction(); @@ -417,6 +419,8 @@ public class ThreadDatabase extends Database { } for (long threadId : threadIds) { + ThreadRecord previous = getThreadRecord(threadId); + smsRecords.addAll(DatabaseFactory.getSmsDatabase(context).setMessagesReadSince(threadId, sinceTimestamp)); mmsRecords.addAll(DatabaseFactory.getMmsDatabase(context).setMessagesReadSince(threadId, sinceTimestamp)); @@ -427,7 +431,12 @@ public class ThreadDatabase extends Database { contentValues.put(UNREAD_COUNT, unreadCount); - db.update(TABLE_NAME, contentValues, ID_WHERE, new String[]{threadId + ""}); + db.update(TABLE_NAME, contentValues, ID_WHERE, SqlUtil.buildArgs(threadId)); + + if (previous != null && previous.isForcedUnread()) { + DatabaseFactory.getRecipientDatabase(context).markNeedsSync(previous.getRecipient().getId()); + needsSync = true; + } } db.setTransactionSuccessful(); @@ -437,6 +446,11 @@ public class ThreadDatabase extends Database { notifyConversationListeners(new HashSet<>(threadIds)); notifyConversationListListeners(); + + if (needsSync) { + StorageSyncHelper.scheduleSyncForDataChange(); + } + return Util.concatenatedList(smsRecords, mmsRecords); } @@ -445,19 +459,22 @@ public class ThreadDatabase extends Database { db.beginTransaction(); try { - ContentValues contentValues = new ContentValues(); + List recipientIds = getRecipientIdsForThreadIds(threadIds); + SqlUtil.Query query = SqlUtil.buildCollectionQuery(ID, threadIds); + ContentValues contentValues = new ContentValues(); + contentValues.put(READ, ReadStatus.FORCED_UNREAD.serialize()); - for (long threadId : threadIds) { - db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] { String.valueOf(threadId) }); - } + db.update(TABLE_NAME, contentValues, query.getWhere(), query.getWhereArgs()); + DatabaseFactory.getRecipientDatabase(context).markNeedsSync(recipientIds); db.setTransactionSuccessful(); } finally { db.endTransaction(); - } - notifyConversationListListeners(); + StorageSyncHelper.scheduleSyncForDataChange(); + notifyConversationListListeners(); + } } @@ -949,6 +966,20 @@ public class ThreadDatabase extends Database { return Recipient.resolved(id); } + public @NonNull List getRecipientIdsForThreadIds(Collection threadIds) { + SQLiteDatabase db = databaseHelper.getReadableDatabase(); + SqlUtil.Query query = SqlUtil.buildCollectionQuery(ID, threadIds); + List ids = new ArrayList<>(threadIds.size()); + + try (Cursor cursor = db.query(TABLE_NAME, new String[] { RECIPIENT_ID }, query.getWhere(), query.getWhereArgs(), null, null, null)) { + while (cursor != null && cursor.moveToNext()) { + ids.add(RecipientId.from(CursorUtil.requireLong(cursor, RECIPIENT_ID))); + } + } + + return ids; + } + public boolean hasThread(@NonNull RecipientId recipientId) { return getThreadIdIfExistsFor(recipientId) > -1; } @@ -964,16 +995,56 @@ public class ThreadDatabase extends Database { } void updateReadState(long threadId) { - int unreadCount = DatabaseFactory.getMmsSmsDatabase(context).getUnreadCount(threadId); + ThreadRecord previous = getThreadRecord(threadId); + int unreadCount = DatabaseFactory.getMmsSmsDatabase(context).getUnreadCount(threadId); ContentValues contentValues = new ContentValues(); - contentValues.put(READ, unreadCount == 0); + contentValues.put(READ, unreadCount == 0 ? ReadStatus.READ.serialize() : ReadStatus.UNREAD.serialize()); contentValues.put(UNREAD_COUNT, unreadCount); - databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues,ID_WHERE, - new String[] {String.valueOf(threadId)}); + databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, ID_WHERE, SqlUtil.buildArgs(threadId)); notifyConversationListListeners(); + + if (previous != null && previous.isForcedUnread()) { + DatabaseFactory.getRecipientDatabase(context).markNeedsSync(previous.getRecipient().getId()); + StorageSyncHelper.scheduleSyncForDataChange(); + } + } + + public void applyStorageSyncUpdate(@NonNull RecipientId recipientId, @NonNull SignalContactRecord record) { + applyStorageSyncUpdate(recipientId, record.isArchived(), record.isForcedUnread()); + } + + public void applyStorageSyncUpdate(@NonNull RecipientId recipientId, @NonNull SignalGroupV1Record record) { + applyStorageSyncUpdate(recipientId, record.isArchived(), record.isForcedUnread()); + } + + public void applyStorageSyncUpdate(@NonNull RecipientId recipientId, @NonNull SignalGroupV2Record record) { + applyStorageSyncUpdate(recipientId, record.isArchived(), record.isForcedUnread()); + } + + public void applyStorageSyncUpdate(@NonNull RecipientId recipientId, @NonNull SignalAccountRecord record) { + applyStorageSyncUpdate(recipientId, record.isNoteToSelfArchived(), record.isNoteToSelfForcedUnread()); + } + + private void applyStorageSyncUpdate(@NonNull RecipientId recipientId, boolean archived, boolean forcedUnread) { + ContentValues values = new ContentValues(); + values.put(ARCHIVED, archived); + + if (forcedUnread) { + values.put(READ, ReadStatus.FORCED_UNREAD.serialize()); + } else { + Long threadId = getThreadIdFor(recipientId); + if (threadId != null) { + int unreadCount = DatabaseFactory.getMmsSmsDatabase(context).getUnreadCount(threadId); + + values.put(READ, unreadCount == 0 ? ReadStatus.READ.serialize() : ReadStatus.UNREAD.serialize()); + values.put(UNREAD_COUNT, unreadCount); + } + } + + databaseHelper.getWritableDatabase().update(TABLE_NAME, values, RECIPIENT_ID + " = ?", SqlUtil.buildArgs(recipientId)); } public boolean update(long threadId, boolean unarchive) { @@ -1404,7 +1475,7 @@ public class ThreadDatabase extends Database { } } - private enum ReadStatus { + enum ReadStatus { READ(1), UNREAD(0), FORCED_UNREAD(2); private final int value; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducer.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducer.java index 3f0de33ff..faa34e65d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducer.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducer.java @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.database.model; import android.content.Context; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; import com.google.protobuf.ByteString; @@ -81,7 +82,11 @@ final class GroupsV2UpdateMessageProducer { } } - List describeChanges(@NonNull DecryptedGroupChange change) { + List describeChanges(@Nullable DecryptedGroup previousGroupState, @NonNull DecryptedGroupChange change) { + if (DecryptedGroup.getDefaultInstance().equals(previousGroupState)) { + previousGroupState = null; + } + List updates = new LinkedList<>(); if (change.getEditor().isEmpty() || UuidUtil.UNKNOWN_UUID.equals(UuidUtil.fromByteString(change.getEditor()))) { @@ -96,7 +101,7 @@ final class GroupsV2UpdateMessageProducer { describeUnknownEditorNewTimer(change, updates); describeUnknownEditorNewAttributeAccess(change, updates); describeUnknownEditorNewMembershipAccess(change, updates); - describeUnknownEditorNewGroupInviteLinkAccess(change, updates); + describeUnknownEditorNewGroupInviteLinkAccess(previousGroupState, change, updates); describeRequestingMembers(change, updates); describeUnknownEditorRequestingMembersApprovals(change, updates); describeUnknownEditorRequestingMembersDeletes(change, updates); @@ -119,7 +124,7 @@ final class GroupsV2UpdateMessageProducer { describeNewTimer(change, updates); describeNewAttributeAccess(change, updates); describeNewMembershipAccess(change, updates); - describeNewGroupInviteLinkAccess(change, updates); + describeNewGroupInviteLinkAccess(previousGroupState, change, updates); describeRequestingMembers(change, updates); describeRequestingMembersApprovals(change, updates); describeRequestingMembersDeletes(change, updates); @@ -509,7 +514,16 @@ final class GroupsV2UpdateMessageProducer { } } - private void describeNewGroupInviteLinkAccess(@NonNull DecryptedGroupChange change, @NonNull List updates) { + private void describeNewGroupInviteLinkAccess(@Nullable DecryptedGroup previousGroupState, + @NonNull DecryptedGroupChange change, + @NonNull List updates) + { + AccessControl.AccessRequired previousAccessControl = null; + + if (previousGroupState != null) { + previousAccessControl = previousGroupState.getAccessControl().getAddFromInviteLink(); + } + boolean editorIsYou = change.getEditor().equals(selfUuidBytes); boolean groupLinkEnabled = false; @@ -517,17 +531,33 @@ final class GroupsV2UpdateMessageProducer { case ANY: groupLinkEnabled = true; if (editorIsYou) { - updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_on_the_group_link_with_admin_approval_off))); + if (previousAccessControl == AccessControl.AccessRequired.ADMINISTRATOR) { + updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_off_admin_approval_for_the_group_link))); + } else { + updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_on_the_group_link_with_admin_approval_off))); + } } else { - updates.add(updateDescription(change.getEditor(), editor -> context.getString(R.string.MessageRecord_s_turned_on_the_group_link_with_admin_approval_off, editor))); + if (previousAccessControl == AccessControl.AccessRequired.ADMINISTRATOR) { + updates.add(updateDescription(change.getEditor(), editor -> context.getString(R.string.MessageRecord_s_turned_off_admin_approval_for_the_group_link, editor))); + } else { + updates.add(updateDescription(change.getEditor(), editor -> context.getString(R.string.MessageRecord_s_turned_on_the_group_link_with_admin_approval_off, editor))); + } } break; case ADMINISTRATOR: groupLinkEnabled = true; if (editorIsYou) { - updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_on_the_group_link_with_admin_approval_on))); + if (previousAccessControl == AccessControl.AccessRequired.ANY) { + updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_on_admin_approval_for_the_group_link))); + } else { + updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_on_the_group_link_with_admin_approval_on))); + } } else { - updates.add(updateDescription(change.getEditor(), editor -> context.getString(R.string.MessageRecord_s_turned_on_the_group_link_with_admin_approval_on, editor))); + if (previousAccessControl == AccessControl.AccessRequired.ANY) { + updates.add(updateDescription(change.getEditor(), editor -> context.getString(R.string.MessageRecord_s_turned_on_admin_approval_for_the_group_link, editor))); + } else { + updates.add(updateDescription(change.getEditor(), editor -> context.getString(R.string.MessageRecord_s_turned_on_the_group_link_with_admin_approval_on, editor))); + } } break; case UNSATISFIABLE: @@ -548,13 +578,30 @@ final class GroupsV2UpdateMessageProducer { } } - private void describeUnknownEditorNewGroupInviteLinkAccess(@NonNull DecryptedGroupChange change, @NonNull List updates) { + private void describeUnknownEditorNewGroupInviteLinkAccess(@Nullable DecryptedGroup previousGroupState, + @NonNull DecryptedGroupChange change, + @NonNull List updates) + { + AccessControl.AccessRequired previousAccessControl = null; + + if (previousGroupState != null) { + previousAccessControl = previousGroupState.getAccessControl().getAddFromInviteLink(); + } + switch (change.getNewInviteLinkAccess()) { case ANY: - updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_turned_on_with_admin_approval_off))); + if (previousAccessControl == AccessControl.AccessRequired.ADMINISTRATOR) { + updates.add(updateDescription(context.getString(R.string.MessageRecord_the_admin_approval_for_the_group_link_has_been_turned_off))); + } else { + updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_turned_on_with_admin_approval_off))); + } break; case ADMINISTRATOR: - updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_turned_on_with_admin_approval_on))); + if (previousAccessControl == AccessControl.AccessRequired.ANY) { + updates.add(updateDescription(context.getString(R.string.MessageRecord_the_admin_approval_for_the_group_link_has_been_turned_on))); + } else { + updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_turned_on_with_admin_approval_on))); + } break; case UNSATISFIABLE: updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_turned_off))); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index 503d217fa..833ab70fc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -177,7 +177,7 @@ public abstract class MessageRecord extends DisplayRecord { GroupsV2UpdateMessageProducer updateMessageProducer = new GroupsV2UpdateMessageProducer(context, descriptionStrategy, Recipient.self().getUuid().get()); if (decryptedGroupV2Context.hasChange() && decryptedGroupV2Context.getGroupState().getRevision() != 0) { - return UpdateDescription.concatWithNewLines(updateMessageProducer.describeChanges(decryptedGroupV2Context.getChange())); + return UpdateDescription.concatWithNewLines(updateMessageProducer.describeChanges(decryptedGroupV2Context.getPreviousGroupState(), decryptedGroupV2Context.getChange())); } else { return updateMessageProducer.describeNewGroup(decryptedGroupV2Context.getGroupState(), decryptedGroupV2Context.getChange()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java index a4adbec66..024b114cd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java @@ -6,6 +6,7 @@ import androidx.annotation.MainThread; import androidx.annotation.NonNull; import org.thoughtcrime.securesms.BuildConfig; +import org.thoughtcrime.securesms.KbsEnclave; import org.thoughtcrime.securesms.messages.IncomingMessageProcessor; import org.thoughtcrime.securesms.messages.BackgroundMessageRetriever; import org.thoughtcrime.securesms.groups.GroupsV2AuthorizationMemoryValueCache; @@ -15,6 +16,7 @@ import org.thoughtcrime.securesms.keyvalue.KeyValueStore; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.megaphone.MegaphoneRepository; import org.thoughtcrime.securesms.notifications.MessageNotifier; +import org.thoughtcrime.securesms.pin.KbsEnclaves; import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess; import org.thoughtcrime.securesms.recipients.LiveRecipientCache; import org.thoughtcrime.securesms.messages.IncomingMessageObserver; @@ -111,11 +113,11 @@ public class ApplicationDependencies { return groupsV2Operations; } - public static synchronized @NonNull KeyBackupService getKeyBackupService() { + public static synchronized @NonNull KeyBackupService getKeyBackupService(@NonNull KbsEnclave enclave) { return getSignalServiceAccountManager().getKeyBackupService(IasKeyStore.getIasKeyStore(application), - BuildConfig.KBS_ENCLAVE_NAME, - Hex.fromStringOrThrow(BuildConfig.KBS_SERVICE_ID), - BuildConfig.KBS_MRENCLAVE, + enclave.getEnclaveName(), + Hex.fromStringOrThrow(enclave.getServiceId()), + enclave.getMrEnclave(), 10); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java index 965a9f00d..8e4c44470 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java @@ -193,7 +193,7 @@ final class GroupManagerV2 { .setEditor(UuidUtil.toByteString(selfUuid)) .build(); - RecipientAndThread recipientAndThread = sendGroupUpdate(masterKey, decryptedGroup, groupChange, null); + RecipientAndThread recipientAndThread = sendGroupUpdate(masterKey, new GroupMutation(null, groupChange, decryptedGroup), null); return new GroupManager.GroupActionResult(recipientAndThread.groupRecipient, recipientAndThread.threadId, @@ -505,16 +505,18 @@ final class GroupManagerV2 { private GroupManager.GroupActionResult commitChange(@NonNull GroupChange.Actions.Builder change) throws GroupNotAMemberException, GroupChangeFailedException, IOException, GroupInsufficientRightsException { - final GroupDatabase.GroupRecord groupRecord = groupDatabase.requireGroup(groupId); - final GroupDatabase.V2GroupProperties v2GroupProperties = groupRecord.requireV2GroupProperties(); - final int nextRevision = v2GroupProperties.getGroupRevision() + 1; - final GroupChange.Actions changeActions = change.setRevision(nextRevision).build(); + final GroupDatabase.GroupRecord groupRecord = groupDatabase.requireGroup(groupId); + final GroupDatabase.V2GroupProperties v2GroupProperties = groupRecord.requireV2GroupProperties(); + final int nextRevision = v2GroupProperties.getGroupRevision() + 1; + final GroupChange.Actions changeActions = change.setRevision(nextRevision).build(); final DecryptedGroupChange decryptedChange; final DecryptedGroup decryptedGroupState; + final DecryptedGroup previousGroupState; try { + previousGroupState = v2GroupProperties.getDecryptedGroup(); decryptedChange = groupOperations.decryptChange(changeActions, selfUuid); - decryptedGroupState = DecryptedGroupUtil.apply(v2GroupProperties.getDecryptedGroup(), decryptedChange); + decryptedGroupState = DecryptedGroupUtil.apply(previousGroupState, decryptedChange); } catch (VerificationFailedException | InvalidGroupStateException | NotAbleToApplyGroupV2ChangeException e) { Log.w(TAG, e); throw new IOException(e); @@ -523,7 +525,8 @@ final class GroupManagerV2 { GroupChange signedGroupChange = commitToServer(changeActions); groupDatabase.update(groupId, decryptedGroupState); - RecipientAndThread recipientAndThread = sendGroupUpdate(groupMasterKey, decryptedGroupState, decryptedChange, signedGroupChange); + GroupMutation groupMutation = new GroupMutation(previousGroupState, decryptedChange, decryptedGroupState); + RecipientAndThread recipientAndThread = sendGroupUpdate(groupMasterKey, groupMutation, signedGroupChange); int newMembersCount = decryptedChange.getNewMembersCount(); List newPendingMembers = getPendingMemberRecipientIds(decryptedChange.getNewPendingMembersList()); @@ -681,7 +684,7 @@ final class GroupManagerV2 { } else if (requestToJoin) { Log.i(TAG, "Requested to join, cannot send update"); - RecipientAndThread recipientAndThread = sendGroupUpdate(groupMasterKey, decryptedGroup, decryptedChange, signedGroupChange); + RecipientAndThread recipientAndThread = sendGroupUpdate(groupMasterKey, new GroupMutation(null, decryptedChange, decryptedGroup), signedGroupChange); return new GroupManager.GroupActionResult(groupRecipient, recipientAndThread.threadId, @@ -706,7 +709,7 @@ final class GroupManagerV2 { System.currentTimeMillis(), decryptedChange); - RecipientAndThread recipientAndThread = sendGroupUpdate(groupMasterKey, decryptedGroup, decryptedChange, signedGroupChange); + RecipientAndThread recipientAndThread = sendGroupUpdate(groupMasterKey, new GroupMutation(null, decryptedChange, decryptedGroup), signedGroupChange); return new GroupManager.GroupActionResult(groupRecipient, recipientAndThread.threadId, @@ -905,7 +908,7 @@ final class GroupManagerV2 { groupDatabase.update(groupId, resetRevision(newGroup, decryptedGroup.getRevision())); - sendGroupUpdate(groupMasterKey, decryptedGroup, decryptedChange, signedGroupChange); + sendGroupUpdate(groupMasterKey, new GroupMutation(decryptedGroup, decryptedChange, newGroup), signedGroupChange); } catch (VerificationFailedException | InvalidGroupStateException | NotAbleToApplyGroupV2ChangeException e) { throw new GroupChangeFailedException(e); } @@ -959,13 +962,12 @@ final class GroupManagerV2 { } private @NonNull RecipientAndThread sendGroupUpdate(@NonNull GroupMasterKey masterKey, - @NonNull DecryptedGroup decryptedGroup, - @Nullable DecryptedGroupChange plainGroupChange, + @NonNull GroupMutation groupMutation, @Nullable GroupChange signedGroupChange) { GroupId.V2 groupId = GroupId.v2(masterKey); Recipient groupRecipient = Recipient.externalGroup(context, groupId); - DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, decryptedGroup, plainGroupChange, signedGroupChange); + DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, groupMutation, signedGroupChange); OutgoingGroupUpdateMessage outgoingMessage = new OutgoingGroupUpdateMessage(groupRecipient, decryptedGroupV2Context, null, @@ -977,8 +979,11 @@ final class GroupManagerV2 { Collections.emptyList(), Collections.emptyList()); + + DecryptedGroupChange plainGroupChange = groupMutation.getGroupChange(); + if (plainGroupChange != null && DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(plainGroupChange)) { - ApplicationDependencies.getJobManager().add(PushGroupSilentUpdateSendJob.create(context, groupId, decryptedGroup, outgoingMessage)); + ApplicationDependencies.getJobManager().add(PushGroupSilentUpdateSendJob.create(context, groupId, groupMutation.getNewGroupState(), outgoingMessage)); return new RecipientAndThread(groupRecipient, -1); } else { long threadId = MessageSender.send(context, outgoingMessage, -1, false, null); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMutation.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMutation.java new file mode 100644 index 000000000..9108d3dbe --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMutation.java @@ -0,0 +1,31 @@ +package org.thoughtcrime.securesms.groups; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.signal.storageservice.protos.groups.local.DecryptedGroup; +import org.signal.storageservice.protos.groups.local.DecryptedGroupChange; + +public final class GroupMutation { + @Nullable private final DecryptedGroup previousGroupState; + @Nullable private final DecryptedGroupChange groupChange; + @NonNull private final DecryptedGroup newGroupState; + + public GroupMutation(@Nullable DecryptedGroup previousGroupState, @Nullable DecryptedGroupChange groupChange, @NonNull DecryptedGroup newGroupState) { + this.previousGroupState = previousGroupState; + this.groupChange = groupChange; + this.newGroupState = newGroupState; + } + + public @Nullable DecryptedGroup getPreviousGroupState() { + return previousGroupState; + } + + public @Nullable DecryptedGroupChange getGroupChange() { + return groupChange; + } + + public @NonNull DecryptedGroup getNewGroupState() { + return newGroupState; + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupProtoUtil.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupProtoUtil.java index 43bab80e3..1a0d290a7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupProtoUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupProtoUtil.java @@ -49,12 +49,13 @@ public final class GroupProtoUtil { } public static DecryptedGroupV2Context createDecryptedGroupV2Context(@NonNull GroupMasterKey masterKey, - @NonNull DecryptedGroup decryptedGroup, - @Nullable DecryptedGroupChange plainGroupChange, + @NonNull GroupMutation groupMutation, @Nullable GroupChange signedServerChange) { - int revision = plainGroupChange != null ? plainGroupChange.getRevision() : decryptedGroup.getRevision(); - SignalServiceProtos.GroupContextV2.Builder contextBuilder = SignalServiceProtos.GroupContextV2.newBuilder() + DecryptedGroupChange plainGroupChange = groupMutation.getGroupChange(); + DecryptedGroup decryptedGroup = groupMutation.getNewGroupState(); + int revision = plainGroupChange != null ? plainGroupChange.getRevision() : decryptedGroup.getRevision(); + SignalServiceProtos.GroupContextV2.Builder contextBuilder = SignalServiceProtos.GroupContextV2.newBuilder() .setMasterKey(ByteString.copyFrom(masterKey.serialize())) .setRevision(revision); @@ -66,6 +67,10 @@ public final class GroupProtoUtil { .setContext(contextBuilder.build()) .setGroupState(decryptedGroup); + if (groupMutation.getPreviousGroupState() != null) { + builder.setPreviousGroupState(groupMutation.getPreviousGroupState()); + } + if (plainGroupChange != null) { builder.setChange(plainGroupChange); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinBottomSheetDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinBottomSheetDialogFragment.java index fa6252616..6f708bec7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinBottomSheetDialogFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinBottomSheetDialogFragment.java @@ -195,7 +195,7 @@ public final class GroupJoinBottomSheetDialogFragment extends BottomSheetDialogF private GroupInviteLinkUrl getGroupInviteLinkUrl() { try { //noinspection ConstantConditions - return GroupInviteLinkUrl.fromUrl(requireArguments().getString(ARG_GROUP_INVITE_LINK_URL)); + return GroupInviteLinkUrl.fromUri(requireArguments().getString(ARG_GROUP_INVITE_LINK_URL)); } catch (GroupInviteLinkUrl.InvalidGroupLinkException | GroupInviteLinkUrl.UnknownGroupLinkVersionException e) { throw new AssertionError(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupInviteLinkUrl.java b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupInviteLinkUrl.java index 7995fe9ed..40ee561b9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupInviteLinkUrl.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupInviteLinkUrl.java @@ -12,8 +12,8 @@ import org.signal.zkgroup.groups.GroupMasterKey; import org.whispersystems.util.Base64UrlSafe; import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; +import java.net.URI; +import java.net.URISyntaxException; public final class GroupInviteLinkUrl { @@ -38,24 +38,24 @@ public final class GroupInviteLinkUrl { * @return null iff not a group url. * @throws InvalidGroupLinkException If group url, but cannot be parsed. */ - public static @Nullable GroupInviteLinkUrl fromUrl(@NonNull String urlString) + public static @Nullable GroupInviteLinkUrl fromUri(@NonNull String urlString) throws InvalidGroupLinkException, UnknownGroupLinkVersionException { - URL url = getGroupUrl(urlString); + URI uri = getGroupUrl(urlString); - if (url == null) { + if (uri == null) { return null; } try { - if (!"/".equals(url.getPath()) && url.getPath().length() > 0) { - throw new InvalidGroupLinkException("No path was expected in url"); + if (!"/".equals(uri.getPath()) && uri.getPath().length() > 0) { + throw new InvalidGroupLinkException("No path was expected in uri"); } - String encoding = url.getRef(); + String encoding = uri.getFragment(); if (encoding == null || encoding.length() == 0) { - throw new InvalidGroupLinkException("No reference was in the url"); + throw new InvalidGroupLinkException("No reference was in the uri"); } byte[] bytes = Base64UrlSafe.decodePaddingAgnostic(encoding); @@ -78,16 +78,23 @@ public final class GroupInviteLinkUrl { } /** - * @return {@link URL} if the host name matches. + * @return {@link URI} if the host name matches. */ - private static URL getGroupUrl(@NonNull String urlString) { + private static URI getGroupUrl(@NonNull String urlString) { try { - URL url = new URL(urlString); + URI url = new URI(urlString); + + if (!"https".equalsIgnoreCase(url.getScheme()) && + !"sgnl".equalsIgnoreCase(url.getScheme())) + { + return null; + } return GROUP_URL_HOST.equalsIgnoreCase(url.getHost()) ? url : null; - } catch (MalformedURLException e) { + + } catch (URISyntaxException e) { return null; } } 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 38276b4cc..37ccde7e9 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 @@ -23,6 +23,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.groups.GroupId; +import org.thoughtcrime.securesms.groups.GroupMutation; import org.thoughtcrime.securesms.groups.GroupNotAMemberException; import org.thoughtcrime.securesms.groups.GroupProtoUtil; import org.thoughtcrime.securesms.groups.GroupsV2Authorization; @@ -226,9 +227,9 @@ public final class GroupsV2StateProcessor { determineProfileSharing(inputGroupState, newLocalState); if (localState != null && localState.getRevision() == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) { Log.i(TAG, "Inserting single update message for restore placeholder"); - insertUpdateMessages(timestamp, Collections.singleton(new LocalGroupLogEntry(newLocalState, null))); + insertUpdateMessages(timestamp, null, Collections.singleton(new LocalGroupLogEntry(newLocalState, null))); } else { - insertUpdateMessages(timestamp, advanceGroupStateResult.getProcessedLogEntries()); + insertUpdateMessages(timestamp, localState, advanceGroupStateResult.getProcessedLogEntries()); } persistLearnedProfileKeys(inputGroupState); @@ -260,7 +261,7 @@ public final class GroupsV2StateProcessor { .addDeleteMembers(UuidUtil.toByteString(selfUuid)) .build(); - DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, simulatedGroupState, simulatedGroupChange, null); + DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, new GroupMutation(decryptedGroup, simulatedGroupChange, simulatedGroupState), null); OutgoingGroupUpdateMessage leaveMessage = new OutgoingGroupUpdateMessage(groupRecipient, decryptedGroupV2Context, null, @@ -362,13 +363,17 @@ public final class GroupsV2StateProcessor { } } - private void insertUpdateMessages(long timestamp, Collection processedLogEntries) { + private void insertUpdateMessages(long timestamp, + @Nullable DecryptedGroup previousGroupState, + Collection processedLogEntries) + { for (LocalGroupLogEntry entry : processedLogEntries) { if (entry.getChange() != null && DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(entry.getChange()) && !DecryptedGroupUtil.changeIsEmpty(entry.getChange())) { Log.d(TAG, "Skipping profile key changes only update message"); } else { - storeMessage(GroupProtoUtil.createDecryptedGroupV2Context(masterKey, entry.getGroup(), entry.getChange(), null), timestamp); + storeMessage(GroupProtoUtil.createDecryptedGroupV2Context(masterKey, new GroupMutation(previousGroupState, entry.getChange(), entry.getGroup()), null), timestamp); } + previousGroupState = entry.getGroup(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ClearFallbackKbsEnclaveJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/ClearFallbackKbsEnclaveJob.java new file mode 100644 index 000000000..834f63f5a --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ClearFallbackKbsEnclaveJob.java @@ -0,0 +1,102 @@ +package org.thoughtcrime.securesms.jobs; + +import androidx.annotation.NonNull; + +import org.thoughtcrime.securesms.KbsEnclave; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.jobmanager.Data; +import org.thoughtcrime.securesms.jobmanager.Job; +import org.thoughtcrime.securesms.jobmanager.JobManager; +import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; +import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.pin.KbsEnclaves; +import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; +import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +/** + * Clears data from an old KBS enclave. + */ +public class ClearFallbackKbsEnclaveJob extends BaseJob { + + public static final String KEY = "ClearFallbackKbsEnclaveJob"; + + private static final String TAG = Log.tag(ClearFallbackKbsEnclaveJob.class); + + private static final String KEY_ENCLAVE_NAME = "enclaveName"; + private static final String KEY_SERVICE_ID = "serviceId"; + private static final String KEY_MR_ENCLAVE = "mrEnclave"; + + private final KbsEnclave enclave; + + ClearFallbackKbsEnclaveJob(@NonNull KbsEnclave enclave) { + this(new Parameters.Builder() + .addConstraint(NetworkConstraint.KEY) + .setLifespan(TimeUnit.DAYS.toMillis(90)) + .setMaxAttempts(Parameters.UNLIMITED) + .setQueue("ClearFallbackKbsEnclaveJob") + .build(), + enclave); + } + + public static void clearAll() { + if (KbsEnclaves.fallbacks().isEmpty()) { + Log.i(TAG, "No fallbacks!"); + return; + } + + JobManager jobManager = ApplicationDependencies.getJobManager(); + + for (KbsEnclave enclave : KbsEnclaves.fallbacks()) { + jobManager.add(new ClearFallbackKbsEnclaveJob(enclave)); + } + } + + private ClearFallbackKbsEnclaveJob(@NonNull Parameters parameters, @NonNull KbsEnclave enclave) { + super(parameters); + this.enclave = enclave; + } + + @Override + public @NonNull String getFactoryKey() { + return KEY; + } + + @Override + public @NonNull Data serialize() { + return new Data.Builder().putString(KEY_ENCLAVE_NAME, enclave.getEnclaveName()) + .putString(KEY_SERVICE_ID, enclave.getServiceId()) + .putString(KEY_MR_ENCLAVE, enclave.getMrEnclave()) + .build(); + } + + @Override + public void onRun() throws IOException, UnauthenticatedResponseException { + Log.i(TAG, "Preparing to delete data from " + enclave.getEnclaveName()); + ApplicationDependencies.getKeyBackupService(enclave).newPinChangeSession().removePin(); + Log.i(TAG, "Successfully deleted the data from " + enclave.getEnclaveName()); + } + + @Override + public boolean onShouldRetry(@NonNull Exception e) { + return true; + } + + @Override + public void onFailure() { + throw new AssertionError("This job should never fail. " + getClass().getSimpleName()); + } + + public static class Factory implements Job.Factory { + @Override + public @NonNull ClearFallbackKbsEnclaveJob create(@NonNull Parameters parameters, @NonNull Data data) { + KbsEnclave enclave = new KbsEnclave(data.getString(KEY_ENCLAVE_NAME), + data.getString(KEY_SERVICE_ID), + data.getString(KEY_MR_ENCLAVE)); + + return new ClearFallbackKbsEnclaveJob(parameters, enclave); + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index 7409d6856..8b85b83e8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -30,6 +30,7 @@ import org.thoughtcrime.securesms.migrations.AvatarIdRemovalMigrationJob; import org.thoughtcrime.securesms.migrations.AvatarMigrationJob; import org.thoughtcrime.securesms.migrations.CachedAttachmentsMigrationJob; import org.thoughtcrime.securesms.migrations.DatabaseMigrationJob; +import org.thoughtcrime.securesms.migrations.KbsEnclaveMigrationJob; import org.thoughtcrime.securesms.migrations.LegacyMigrationJob; import org.thoughtcrime.securesms.migrations.MigrationCompleteJob; import org.thoughtcrime.securesms.migrations.PassingMigrationJob; @@ -62,9 +63,11 @@ public final class JobManagerFactories { put(AvatarGroupsV1DownloadJob.KEY, new AvatarGroupsV1DownloadJob.Factory()); put(AvatarGroupsV2DownloadJob.KEY, new AvatarGroupsV2DownloadJob.Factory()); put(CleanPreKeysJob.KEY, new CleanPreKeysJob.Factory()); + put(ClearFallbackKbsEnclaveJob.KEY, new ClearFallbackKbsEnclaveJob.Factory()); put(CreateSignedPreKeyJob.KEY, new CreateSignedPreKeyJob.Factory()); put(DirectoryRefreshJob.KEY, new DirectoryRefreshJob.Factory()); put(FcmRefreshJob.KEY, new FcmRefreshJob.Factory()); + put(KbsEnclaveMigrationWorkerJob.KEY, new KbsEnclaveMigrationWorkerJob.Factory()); put(LeaveGroupJob.KEY, new LeaveGroupJob.Factory()); put(LocalBackupJob.KEY, new LocalBackupJob.Factory()); put(MmsDownloadJob.KEY, new MmsDownloadJob.Factory()); @@ -132,6 +135,7 @@ public final class JobManagerFactories { put(AvatarMigrationJob.KEY, new AvatarMigrationJob.Factory()); put(CachedAttachmentsMigrationJob.KEY, new CachedAttachmentsMigrationJob.Factory()); put(DatabaseMigrationJob.KEY, new DatabaseMigrationJob.Factory()); + put(KbsEnclaveMigrationJob.KEY, new KbsEnclaveMigrationJob.Factory()); put(LegacyMigrationJob.KEY, new LegacyMigrationJob.Factory()); put(MigrationCompleteJob.KEY, new MigrationCompleteJob.Factory()); put(PinOptOutMigration.KEY, new PinOptOutMigration.Factory()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/KbsEnclaveMigrationWorkerJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/KbsEnclaveMigrationWorkerJob.java new file mode 100644 index 000000000..c9c046e1c --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/KbsEnclaveMigrationWorkerJob.java @@ -0,0 +1,85 @@ +package org.thoughtcrime.securesms.jobs; + +import androidx.annotation.NonNull; + +import org.thoughtcrime.securesms.jobmanager.Data; +import org.thoughtcrime.securesms.jobmanager.Job; +import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; +import org.thoughtcrime.securesms.keyvalue.SignalStore; +import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.migrations.KbsEnclaveMigrationJob; +import org.thoughtcrime.securesms.pin.PinState; +import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException; + +import java.io.IOException; + +/** + * Should only be enqueued by {@link KbsEnclaveMigrationJob}. Does the actual work of migrating KBS + * data to the new enclave and deleting it from the old enclave(s). + */ +public class KbsEnclaveMigrationWorkerJob extends BaseJob { + + public static final String KEY = "KbsEnclaveMigrationWorkerJob"; + + private static final String TAG = Log.tag(KbsEnclaveMigrationWorkerJob.class); + + public KbsEnclaveMigrationWorkerJob() { + this(new Parameters.Builder() + .addConstraint(NetworkConstraint.KEY) + .setLifespan(Parameters.IMMORTAL) + .setMaxAttempts(Parameters.UNLIMITED) + .setQueue("KbsEnclaveMigrationWorkerJob") + .setMaxInstances(1) + .build()); + } + + private KbsEnclaveMigrationWorkerJob(@NonNull Parameters parameters) { + super(parameters); + } + + @Override + public @NonNull String getFactoryKey() { + return KEY; + } + + @Override + public @NonNull Data serialize() { + return Data.EMPTY; + } + + @Override + public void onRun() throws IOException, UnauthenticatedResponseException { + String pin = SignalStore.kbsValues().getPin(); + + if (SignalStore.kbsValues().hasOptedOut()) { + Log.w(TAG, "Opted out of KBS! Nothing to migrate."); + return; + } + + if (pin == null) { + Log.w(TAG, "No PIN available! Can't migrate!"); + return; + } + + PinState.onMigrateToNewEnclave(pin); + Log.i(TAG, "Migration successful!"); + } + + @Override + public boolean onShouldRetry(@NonNull Exception e) { + return e instanceof IOException || + e instanceof UnauthenticatedResponseException; + } + + @Override + public void onFailure() { + throw new AssertionError("This job should never fail. " + getClass().getSimpleName()); + } + + public static class Factory implements Job.Factory { + @Override + public @NonNull KbsEnclaveMigrationWorkerJob create(@NonNull Parameters parameters, @NonNull Data data) { + return new KbsEnclaveMigrationWorkerJob(parameters); + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java index a78f19164..a47a0728a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java @@ -512,7 +512,7 @@ public final class PushProcessMessageJob extends BaseJob { Intent intent = new Intent(context, WebRtcCallService.class); Recipient recipient = Recipient.externalHighTrustPush(context, content.getSender()); RemotePeer remotePeer = new RemotePeer(recipient.getId()); - byte[] remoteIdentityKey = recipient.getIdentityKey(); + byte[] remoteIdentityKey = DatabaseFactory.getIdentityDatabase(context).getIdentity(recipient.getId()).transform(record -> record.getIdentityKey().serialize()).orNull(); intent.setAction(WebRtcCallService.ACTION_RECEIVE_OFFER) .putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId()) @@ -538,7 +538,7 @@ public final class PushProcessMessageJob extends BaseJob { Intent intent = new Intent(context, WebRtcCallService.class); Recipient recipient = Recipient.externalHighTrustPush(context, content.getSender()); RemotePeer remotePeer = new RemotePeer(recipient.getId()); - byte[] remoteIdentityKey = recipient.getIdentityKey(); + byte[] remoteIdentityKey = DatabaseFactory.getIdentityDatabase(context).getIdentity(recipient.getId()).transform(record -> record.getIdentityKey().serialize()).orNull(); intent.setAction(WebRtcCallService.ACTION_RECEIVE_ANSWER) .putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId()) @@ -1830,7 +1830,7 @@ public final class PushProcessMessageJob extends BaseJob { Optional groupId = GroupUtil.idFromGroupContext(message.getGroupContext()); if (groupId.isPresent() && groupDatabase.isUnknownGroup(groupId.get())) { - return false; + return sender.isBlocked(); } boolean isTextMessage = message.getBody().isPresent(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageForcePushJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageForcePushJob.java index 362ffa903..32d155fa0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageForcePushJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageForcePushJob.java @@ -83,11 +83,10 @@ public class StorageForcePushJob extends BaseJob { long newVersion = currentVersion + 1; Map newContactStorageIds = generateContactStorageIds(oldContactStorageIds); - Set archivedRecipients = DatabaseFactory.getThreadDatabase(context).getArchivedRecipients(); List inserts = Stream.of(oldContactStorageIds.keySet()) .map(recipientDatabase::getRecipientSettingsForSync) .withoutNulls() - .map(s -> StorageSyncModels.localToRemoteRecord(s, Objects.requireNonNull(newContactStorageIds.get(s.getId())).getRaw(), archivedRecipients)) + .map(s -> StorageSyncModels.localToRemoteRecord(s, Objects.requireNonNull(newContactStorageIds.get(s.getId())).getRaw())) .toList(); SignalStorageRecord accountRecord = StorageSyncHelper.buildAccountRecord(context, Recipient.self().fresh()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.java index 969aa536e..a4abe45fc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.java @@ -152,8 +152,7 @@ public class StorageSyncJob extends BaseJob { if (!keyDifference.isEmpty()) { Log.i(TAG, "[Remote Newer] There's a difference in keys. Local-only: " + keyDifference.getLocalOnlyKeys().size() + ", Remote-only: " + keyDifference.getRemoteOnlyKeys().size()); - Set archivedRecipients = DatabaseFactory.getThreadDatabase(context).getArchivedRecipients(); - List localOnly = buildLocalStorageRecords(context, keyDifference.getLocalOnlyKeys(), archivedRecipients); + List localOnly = buildLocalStorageRecords(context, keyDifference.getLocalOnlyKeys()); List remoteOnly = accountManager.readStorageRecords(storageServiceKey, keyDifference.getRemoteOnlyKeys()); MergeResult mergeResult = StorageSyncHelper.resolveConflict(remoteOnly, localOnly); WriteOperationResult writeOperationResult = StorageSyncHelper.createWriteOperation(remoteManifest.get().getVersion(), allLocalStorageKeys, mergeResult); @@ -184,6 +183,8 @@ public class StorageSyncJob extends BaseJob { } remoteManifestVersion = writeOperationResult.getManifest().getVersion(); + + needsMultiDeviceSync = true; } else { Log.i(TAG, "[Remote Newer] After resolving the conflict, all changes are local. No remote writes needed."); } @@ -191,7 +192,6 @@ public class StorageSyncJob extends BaseJob { recipientDatabase.applyStorageSyncUpdates(mergeResult.getLocalContactInserts(), mergeResult.getLocalContactUpdates(), mergeResult.getLocalGroupV1Inserts(), mergeResult.getLocalGroupV1Updates(), mergeResult.getLocalGroupV2Inserts(), mergeResult.getLocalGroupV2Updates()); storageKeyDatabase.applyStorageSyncUpdates(mergeResult.getLocalUnknownInserts(), mergeResult.getLocalUnknownDeletes()); StorageSyncHelper.applyAccountStorageSyncUpdates(context, mergeResult.getLocalAccountUpdate()); - needsMultiDeviceSync = true; Log.i(TAG, "[Remote Newer] Updating local manifest version to: " + remoteManifestVersion); TextSecurePreferences.setStorageManifestVersion(context, remoteManifestVersion); @@ -212,15 +212,13 @@ public class StorageSyncJob extends BaseJob { List pendingDeletions = recipientDatabase.getPendingRecipientSyncDeletions(); Optional pendingAccountInsert = StorageSyncHelper.getPendingAccountSyncInsert(context, self); Optional pendingAccountUpdate = StorageSyncHelper.getPendingAccountSyncUpdate(context, self); - Set archivedRecipients = DatabaseFactory.getThreadDatabase(context).getArchivedRecipients(); Optional localWriteResult = StorageSyncHelper.buildStorageUpdatesForLocal(localManifestVersion, allLocalStorageKeys, pendingUpdates, pendingInsertions, pendingDeletions, pendingAccountUpdate, - pendingAccountInsert, - archivedRecipients); + pendingAccountInsert); if (localWriteResult.isPresent()) { Log.i(TAG, String.format(Locale.ENGLISH, "[Local Changes] Local changes present. %d updates, %d inserts, %d deletes, account update: %b, account insert: %b.", pendingUpdates.size(), pendingInsertions.size(), pendingDeletions.size(), pendingAccountUpdate.isPresent(), pendingAccountInsert.isPresent())); @@ -273,7 +271,7 @@ public class StorageSyncJob extends BaseJob { DatabaseFactory.getStorageKeyDatabase(context).getAllKeys()); } - private static @NonNull List buildLocalStorageRecords(@NonNull Context context, @NonNull List ids, @NonNull Set archivedRecipients) { + private static @NonNull List buildLocalStorageRecords(@NonNull Context context, @NonNull List ids) { Recipient self = Recipient.self().fresh(); RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context); StorageKeyDatabase storageKeyDatabase = DatabaseFactory.getStorageKeyDatabase(context); @@ -287,10 +285,10 @@ public class StorageSyncJob extends BaseJob { case ManifestRecord.Identifier.Type.GROUPV2_VALUE: RecipientSettings settings = recipientDatabase.getByStorageId(id.getRaw()); if (settings != null) { - if (settings.getGroupType() == RecipientDatabase.GroupType.SIGNAL_V2 && settings.getGroupMasterKey() == null) { + if (settings.getGroupType() == RecipientDatabase.GroupType.SIGNAL_V2 && settings.getSyncExtras().getGroupMasterKey() == null) { Log.w(TAG, "Missing master key on gv2 recipient"); } else { - records.add(StorageSyncModels.localToRemoteRecord(settings, archivedRecipients)); + records.add(StorageSyncModels.localToRemoteRecord(settings)); } } else { Log.w(TAG, "Missing local recipient model! Type: " + id.getType()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/KbsValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/KbsValues.java index e2fb45106..8dd450f2c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/KbsValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/KbsValues.java @@ -137,6 +137,10 @@ public final class KbsValues extends SignalStoreValues { } } + public synchronized @Nullable String getPin() { + return getString(PIN, null); + } + public synchronized @Nullable String getLocalPinHash() { return getString(LOCK_LOCAL_PIN_HASH, null); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java index 136fb581a..d5e667caa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java @@ -252,7 +252,7 @@ public class LinkPreviewRepository { { SignalExecutors.UNBOUNDED.execute(() -> { try { - GroupInviteLinkUrl groupInviteLinkUrl = GroupInviteLinkUrl.fromUrl(groupUrl); + GroupInviteLinkUrl groupInviteLinkUrl = GroupInviteLinkUrl.fromUri(groupUrl); if (groupInviteLinkUrl == null) { throw new AssertionError(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestRepository.java b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestRepository.java index 9ee8995bb..522baa6ed 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestRepository.java @@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.groups.GroupChangeException; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupManager; import org.thoughtcrime.securesms.groups.ui.GroupChangeErrorCallback; import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason; @@ -229,6 +230,10 @@ final class MessageRequestRepository { }); } + boolean isPendingMember(@NonNull GroupId.V2 groupId) { + return DatabaseFactory.getGroupDatabase(context).isPendingMember(groupId, Recipient.self()); + } + enum MessageRequestState { /** * Message request permission does not need to be gained at this time. diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestViewModel.java index 749f1bef9..3538894ac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestViewModel.java @@ -5,6 +5,7 @@ import android.content.Context; import androidx.annotation.MainThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.Transformations; @@ -19,6 +20,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.SingleLiveEvent; import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; import org.thoughtcrime.securesms.util.livedata.LiveDataTriple; +import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; import java.util.Collections; import java.util.List; @@ -28,6 +30,7 @@ public class MessageRequestViewModel extends ViewModel { private final SingleLiveEvent status = new SingleLiveEvent<>(); private final SingleLiveEvent failures = new SingleLiveEvent<>(); private final MutableLiveData recipient = new MutableLiveData<>(); + private final LiveData messageData; private final MutableLiveData> groups = new MutableLiveData<>(Collections.emptyList()); private final MutableLiveData memberCount = new MutableLiveData<>(GroupMemberCount.ZERO); private final MutableLiveData displayState = new MutableLiveData<>(); @@ -46,7 +49,8 @@ public class MessageRequestViewModel extends ViewModel { }; private MessageRequestViewModel(MessageRequestRepository repository) { - this.repository = repository; + this.repository = repository; + this.messageData = LiveDataUtil.mapAsync(recipient, this::createMessageDataForRecipient); } public void setConversationInfo(@NonNull RecipientId recipientId, long threadId) { @@ -77,6 +81,10 @@ public class MessageRequestViewModel extends ViewModel { return recipient; } + public LiveData getMessageData() { + return messageData; + } + public LiveData getRecipientInfo() { return recipientInfo; } @@ -152,6 +160,29 @@ public class MessageRequestViewModel extends ViewModel { repository.getMemberCount(liveRecipient.getId(), memberCount::postValue); } + @WorkerThread + private @NonNull MessageData createMessageDataForRecipient(@NonNull Recipient recipient) { + if (recipient.isBlocked()) { + if (recipient.isGroup()) { + return new MessageData(recipient, MessageClass.BLOCKED_GROUP); + } else { + return new MessageData(recipient, MessageClass.BLOCKED_INDIVIDUAL); + } + } else if (recipient.isGroup()) { + if (recipient.isPushV2Group()) { + if (repository.isPendingMember(recipient.requireGroupId().requireV2())) { + return new MessageData(recipient, MessageClass.GROUP_V2_INVITE); + } else { + return new MessageData(recipient, MessageClass.GROUP_V2_ADD); + } + } else { + return new MessageData(recipient, MessageClass.GROUP_V1); + } + } else { + return new MessageData(recipient, MessageClass.INDIVIDUAL); + } + } + @SuppressWarnings("ConstantConditions") private void loadMessageRequestAccepted(@NonNull Recipient recipient) { if (recipient.isBlocked()) { @@ -218,6 +249,33 @@ public class MessageRequestViewModel extends ViewModel { DISPLAY_MESSAGE_REQUEST, DISPLAY_LEGACY, DISPLAY_NONE } + public enum MessageClass { + BLOCKED_INDIVIDUAL, + BLOCKED_GROUP, + GROUP_V1, + GROUP_V2_INVITE, + GROUP_V2_ADD, + INDIVIDUAL + } + + public static final class MessageData { + private final Recipient recipient; + private final MessageClass messageClass; + + public MessageData(@NonNull Recipient recipient, @NonNull MessageClass messageClass) { + this.recipient = recipient; + this.messageClass = messageClass; + } + + public @NonNull Recipient getRecipient() { + return recipient; + } + + public @NonNull MessageClass getMessageClass() { + return messageClass; + } + } + public static class Factory implements ViewModelProvider.Factory { private final Context context; diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsBottomView.java b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsBottomView.java index cf18d3946..e7e4a7c8b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsBottomView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsBottomView.java @@ -60,23 +60,33 @@ public class MessageRequestsBottomView extends ConstraintLayout { busyIndicator = findViewById(R.id.message_request_busy_indicator); } - public void setRecipient(@NonNull Recipient recipient) { - if (recipient.isBlocked()) { - if (recipient.isGroup()) { + public void setMessageData(@NonNull MessageRequestViewModel.MessageData messageData) { + Recipient recipient = messageData.getRecipient(); + + switch (messageData.getMessageClass()) { + case BLOCKED_INDIVIDUAL: + question.setText(HtmlCompat.fromHtml(getContext().getString(R.string.MessageRequestBottomView_do_you_want_to_let_s_message_you_wont_receive_any_messages_until_you_unblock_them, + HtmlUtil.bold(recipient.getShortDisplayName(getContext()))), 0)); + setActiveInactiveGroups(blockedButtons, normalButtons); + break; + case BLOCKED_GROUP: question.setText(R.string.MessageRequestBottomView_unblock_this_group_and_share_your_name_and_photo_with_its_members); - } else { - String name = recipient.getShortDisplayName(getContext()); - question.setText(HtmlCompat.fromHtml(getContext().getString(R.string.MessageRequestBottomView_do_you_want_to_let_s_message_you_wont_receive_any_messages_until_you_unblock_them, HtmlUtil.bold(name)), 0)); - } - setActiveInactiveGroups(blockedButtons, normalButtons); - } else { - if (recipient.isGroup()) { + setActiveInactiveGroups(blockedButtons, normalButtons); + break; + case GROUP_V1: + case GROUP_V2_INVITE: question.setText(R.string.MessageRequestBottomView_do_you_want_to_join_this_group_they_wont_know_youve_seen_their_messages_until_you_accept); - } else { - String name = recipient.getShortDisplayName(getContext()); - question.setText(HtmlCompat.fromHtml(getContext().getString(R.string.MessageRequestBottomView_do_you_want_to_let_s_message_you_they_wont_know_youve_seen_their_messages_until_you_accept, HtmlUtil.bold(name)), 0)); - } - setActiveInactiveGroups(normalButtons, blockedButtons); + setActiveInactiveGroups(normalButtons, blockedButtons); + break; + case GROUP_V2_ADD: + question.setText(R.string.MessageRequestBottomView_join_this_group_they_wont_know_youve_seen_their_messages_until_you_accept); + setActiveInactiveGroups(normalButtons, blockedButtons); + break; + case INDIVIDUAL: + question.setText(HtmlCompat.fromHtml(getContext().getString(R.string.MessageRequestBottomView_do_you_want_to_let_s_message_you_they_wont_know_youve_seen_their_messages_until_you_accept, + HtmlUtil.bold(recipient.getShortDisplayName(getContext()))), 0)); + setActiveInactiveGroups(normalButtons, blockedButtons); + break; } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/KbsEnclaveMigrationJob.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/KbsEnclaveMigrationJob.java new file mode 100644 index 000000000..dff47afb7 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/KbsEnclaveMigrationJob.java @@ -0,0 +1,53 @@ +package org.thoughtcrime.securesms.migrations; + +import androidx.annotation.NonNull; + +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.jobmanager.Data; +import org.thoughtcrime.securesms.jobmanager.Job; +import org.thoughtcrime.securesms.jobs.KbsEnclaveMigrationWorkerJob; + +/** + * A job to be run whenever we add a new KBS enclave. In order to prevent this moderately-expensive + * task from blocking the network for too long, this task simply enqueues another non-migration job, + * {@link KbsEnclaveMigrationWorkerJob}, to do the heavy lifting. + */ +public class KbsEnclaveMigrationJob extends MigrationJob { + + public static final String KEY = "KbsEnclaveMigrationJob"; + + KbsEnclaveMigrationJob() { + this(new Parameters.Builder().build()); + } + + private KbsEnclaveMigrationJob(@NonNull Parameters parameters) { + super(parameters); + } + + @Override + public boolean isUiBlocking() { + return false; + } + + @Override + public @NonNull String getFactoryKey() { + return KEY; + } + + @Override + public void performMigration() { + ApplicationDependencies.getJobManager().add(new KbsEnclaveMigrationWorkerJob()); + } + + @Override + boolean shouldRetry(@NonNull Exception e) { + return false; + } + + public static class Factory implements Job.Factory { + @Override + public @NonNull KbsEnclaveMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) { + return new KbsEnclaveMigrationJob(parameters); + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/AudioSlide.java b/app/src/main/java/org/thoughtcrime/securesms/mms/AudioSlide.java index 29f83cfb2..ae561f0fb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/AudioSlide.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/AudioSlide.java @@ -53,7 +53,7 @@ public class AudioSlide extends Slide { @Override public boolean hasImage() { - return true; + return false; } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/KbsEnclaves.java b/app/src/main/java/org/thoughtcrime/securesms/pin/KbsEnclaves.java new file mode 100644 index 000000000..df0d209ed --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/pin/KbsEnclaves.java @@ -0,0 +1,26 @@ +package org.thoughtcrime.securesms.pin; + +import androidx.annotation.NonNull; + +import org.thoughtcrime.securesms.BuildConfig; +import org.thoughtcrime.securesms.KbsEnclave; +import org.thoughtcrime.securesms.util.Util; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public final class KbsEnclaves { + + public static @NonNull KbsEnclave current() { + return BuildConfig.KBS_ENCLAVE; + } + + public static @NonNull List all() { + return Util.join(Collections.singletonList(BuildConfig.KBS_ENCLAVE), fallbacks()); + } + + public static @NonNull List fallbacks() { + return Arrays.asList(BuildConfig.KBS_FALLBACKS); + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreRepository.java b/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreRepository.java index 5ac818d12..7b19c9d4b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreRepository.java @@ -1,11 +1,15 @@ package org.thoughtcrime.securesms.pin; -import androidx.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.thoughtcrime.securesms.KbsEnclave; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobs.StorageAccountRestoreJob; import org.thoughtcrime.securesms.jobs.StorageSyncJob; -import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.registration.service.KeyBackupSystemWrongPinException; import org.thoughtcrime.securesms.util.Stopwatch; @@ -21,32 +25,58 @@ import java.util.Objects; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; -class PinRestoreRepository { +public class PinRestoreRepository { private static final String TAG = Log.tag(PinRestoreRepository.class); - private final Executor executor = SignalExecutors.UNBOUNDED; - private final KeyBackupService kbs = ApplicationDependencies.getKeyBackupService(); + private final Executor executor = SignalExecutors.UNBOUNDED; void getToken(@NonNull Callback> callback) { executor.execute(() -> { try { - String authorization = kbs.getAuthorization(); - TokenResponse token = kbs.getToken(authorization); - TokenData tokenData = new TokenData(authorization, token); - callback.onComplete(Optional.of(tokenData)); + callback.onComplete(Optional.fromNullable(getTokenSync(null))); } catch (IOException e) { callback.onComplete(Optional.absent()); } }); } + /** + * @param authorization If this is being called before the user is registered (i.e. as part of + * reglock), you must pass in an authorization token that can be used to + * retrieve a backup. Otherwise, pass in null and we'll fetch one. + */ + public @NonNull TokenData getTokenSync(@Nullable String authorization) throws IOException { + TokenData firstKnownTokenData = null; + + for (KbsEnclave enclave : KbsEnclaves.all()) { + KeyBackupService kbs = ApplicationDependencies.getKeyBackupService(enclave); + + authorization = authorization == null ? kbs.getAuthorization() : authorization; + + TokenResponse token = kbs.getToken(authorization); + TokenData tokenData = new TokenData(enclave, authorization, token); + + if (tokenData.getTriesRemaining() > 0) { + Log.i(TAG, "Found data! " + enclave.getEnclaveName()); + return tokenData; + } else if (firstKnownTokenData == null) { + Log.i(TAG, "No data, but storing as the first response. " + enclave.getEnclaveName()); + firstKnownTokenData = tokenData; + } else { + Log.i(TAG, "No data, and we already have a 'first response'. " + enclave.getEnclaveName()); + } + } + + return Objects.requireNonNull(firstKnownTokenData); + } + void submitPin(@NonNull String pin, @NonNull TokenData tokenData, @NonNull Callback callback) { executor.execute(() -> { try { Stopwatch stopwatch = new Stopwatch("PinSubmission"); - KbsPinData kbsData = PinState.restoreMasterKey(pin, tokenData.basicAuth, tokenData.tokenResponse); + KbsPinData kbsData = PinState.restoreMasterKey(pin, tokenData.getEnclave(), tokenData.getBasicAuth(), tokenData.getTokenResponse()); PinState.onSignalPinRestore(ApplicationDependencies.getApplication(), Objects.requireNonNull(kbsData), pin); stopwatch.split("MasterKey"); @@ -64,7 +94,7 @@ class PinRestoreRepository { } catch (KeyBackupSystemNoDataException e) { callback.onComplete(new PinResultData(PinResult.LOCKED, tokenData)); } catch (KeyBackupSystemWrongPinException e) { - callback.onComplete(new PinResultData(PinResult.INCORRECT, new TokenData(tokenData.basicAuth, e.getTokenResponse()))); + callback.onComplete(new PinResultData(PinResult.INCORRECT, TokenData.withResponse(tokenData, e.getTokenResponse()))); } }); } @@ -73,18 +103,81 @@ class PinRestoreRepository { void onComplete(@NonNull T value); } - static class TokenData { + public static class TokenData implements Parcelable { + private final KbsEnclave enclave; private final String basicAuth; private final TokenResponse tokenResponse; - TokenData(@NonNull String basicAuth, @NonNull TokenResponse tokenResponse) { + TokenData(@NonNull KbsEnclave enclave, @NonNull String basicAuth, @NonNull TokenResponse tokenResponse) { + this.enclave = enclave; this.basicAuth = basicAuth; this.tokenResponse = tokenResponse; } - int getTriesRemaining() { + private TokenData(Parcel in) { + //noinspection ConstantConditions + this.enclave = new KbsEnclave(in.readString(), in.readString(), in.readString()); + this.basicAuth = in.readString(); + + byte[] backupId = new byte[0]; + byte[] token = new byte[0]; + + in.readByteArray(backupId); + in.readByteArray(token); + + this.tokenResponse = new TokenResponse(backupId, token, in.readInt()); + } + + public static @NonNull TokenData withResponse(@NonNull TokenData data, @NonNull TokenResponse response) { + return new TokenData(data.getEnclave(), data.getBasicAuth(), response); + } + + public int getTriesRemaining() { return tokenResponse.getTries(); } + + public @NonNull String getBasicAuth() { + return basicAuth; + } + + public @NonNull TokenResponse getTokenResponse() { + return tokenResponse; + } + + public @NonNull KbsEnclave getEnclave() { + return enclave; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(enclave.getEnclaveName()); + dest.writeString(enclave.getServiceId()); + dest.writeString(enclave.getMrEnclave()); + + dest.writeString(basicAuth); + + dest.writeByteArray(tokenResponse.getBackupId()); + dest.writeByteArray(tokenResponse.getToken()); + dest.writeInt(tokenResponse.getTries()); + } + + public static final Creator CREATOR = new Creator() { + @Override + public TokenData createFromParcel(Parcel in) { + return new TokenData(in); + } + + @Override + public TokenData[] newArray(int size) { + return new TokenData[size]; + } + }; + } static class PinResultData { @@ -92,7 +185,7 @@ class PinRestoreRepository { private final TokenData tokenData; PinResultData(@NonNull PinResult result, @NonNull TokenData tokenData) { - this.result = result; + this.result = result; this.tokenData = tokenData; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/PinState.java b/app/src/main/java/org/thoughtcrime/securesms/pin/PinState.java index 20faacb49..0c9cbd520 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pin/PinState.java +++ b/app/src/main/java/org/thoughtcrime/securesms/pin/PinState.java @@ -6,8 +6,11 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; +import org.thoughtcrime.securesms.BuildConfig; +import org.thoughtcrime.securesms.KbsEnclave; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobmanager.JobTracker; +import org.thoughtcrime.securesms.jobs.ClearFallbackKbsEnclaveJob; import org.thoughtcrime.securesms.jobs.RefreshAttributesJob; import org.thoughtcrime.securesms.jobs.StorageForcePushJob; import org.thoughtcrime.securesms.keyvalue.KbsValues; @@ -26,12 +29,14 @@ import org.whispersystems.signalservice.api.KeyBackupServicePinException; import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException; import org.whispersystems.signalservice.api.kbs.HashedPin; import org.whispersystems.signalservice.api.kbs.MasterKey; +import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException; import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse; import java.io.IOException; import java.security.SecureRandom; import java.util.Arrays; +import java.util.List; import java.util.Locale; import java.util.concurrent.TimeUnit; @@ -46,6 +51,7 @@ public final class PinState { * Does not affect {@link PinState}. */ public static synchronized @Nullable KbsPinData restoreMasterKey(@Nullable String pin, + @NonNull KbsEnclave enclave, @Nullable String basicStorageCredentials, @NonNull TokenResponse tokenResponse) throws IOException, KeyBackupSystemWrongPinException, KeyBackupSystemNoDataException @@ -58,20 +64,31 @@ public final class PinState { throw new AssertionError("Cannot restore KBS key, no storage credentials supplied"); } - KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService(); + Log.i(TAG, "Preparing to restore from " + enclave.getEnclaveName()); + return restoreMasterKeyFromEnclave(enclave, pin, basicStorageCredentials, tokenResponse); + } - Log.i(TAG, "Opening key backup service session"); - KeyBackupService.RestoreSession session = keyBackupService.newRegistrationSession(basicStorageCredentials, tokenResponse); + private static @NonNull KbsPinData restoreMasterKeyFromEnclave(@NonNull KbsEnclave enclave, + @NonNull String pin, + @NonNull String basicStorageCredentials, + @NonNull TokenResponse tokenResponse) + throws IOException, KeyBackupSystemWrongPinException, KeyBackupSystemNoDataException + { + KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService(enclave); + KeyBackupService.RestoreSession session = keyBackupService.newRegistrationSession(basicStorageCredentials, tokenResponse); try { Log.i(TAG, "Restoring pin from KBS"); + HashedPin hashedPin = PinHashing.hashPin(pin, session); KbsPinData kbsData = session.restorePin(hashedPin); + if (kbsData != null) { Log.i(TAG, "Found registration lock token on KBS."); } else { throw new AssertionError("Null not expected"); } + return kbsData; } catch (UnauthenticatedResponseException e) { Log.w(TAG, "Failed to restore key", e); @@ -90,7 +107,7 @@ public final class PinState { @Nullable String pin, boolean hasPinToRestore) { - Log.i(TAG, "onNewRegistration()"); + Log.i(TAG, "onRegistration()"); TextSecurePreferences.setV1RegistrationLockPin(context, pin); @@ -106,7 +123,8 @@ public final class PinState { SignalStore.kbsValues().setV2RegistrationLockEnabled(true); SignalStore.kbsValues().setKbsMasterKey(kbsData, pin); SignalStore.pinValues().resetPinReminders(); - resetPinRetryCount(context, pin, kbsData); + resetPinRetryCount(context, pin); + ClearFallbackKbsEnclaveJob.clearAll(); } else if (hasPinToRestore) { Log.i(TAG, "Has a PIN to restore."); SignalStore.kbsValues().clearRegistrationLockAndPin(); @@ -131,7 +149,8 @@ public final class PinState { SignalStore.kbsValues().setV2RegistrationLockEnabled(false); SignalStore.pinValues().resetPinReminders(); SignalStore.storageServiceValues().setNeedsAccountRestore(false); - resetPinRetryCount(context, pin, kbsData); + resetPinRetryCount(context, pin); + ClearFallbackKbsEnclaveJob.clearAll(); updateState(buildInferredStateFromOtherFields()); } @@ -158,7 +177,7 @@ public final class PinState { KbsValues kbsValues = SignalStore.kbsValues(); boolean isFirstPin = !kbsValues.hasPin() || kbsValues.hasOptedOut(); MasterKey masterKey = kbsValues.getOrCreateMasterKey(); - KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService(); + KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService(KbsEnclaves.current()); KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession(); HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession); KbsPinData kbsData = pinChangeSession.setPin(hashedPin, masterKey); @@ -217,7 +236,7 @@ public final class PinState { assertState(State.PIN_WITH_REGISTRATION_LOCK_DISABLED); SignalStore.kbsValues().setV2RegistrationLockEnabled(false); - ApplicationDependencies.getKeyBackupService() + ApplicationDependencies.getKeyBackupService(KbsEnclaves.current()) .newPinChangeSession(SignalStore.kbsValues().getRegistrationLockTokenResponse()) .enableRegistrationLock(SignalStore.kbsValues().getOrCreateMasterKey()); SignalStore.kbsValues().setV2RegistrationLockEnabled(true); @@ -240,7 +259,7 @@ public final class PinState { assertState(State.PIN_WITH_REGISTRATION_LOCK_ENABLED); SignalStore.kbsValues().setV2RegistrationLockEnabled(true); - ApplicationDependencies.getKeyBackupService() + ApplicationDependencies.getKeyBackupService(KbsEnclaves.current()) .newPinChangeSession(SignalStore.kbsValues().getRegistrationLockTokenResponse()) .disableRegistrationLock(); SignalStore.kbsValues().setV2RegistrationLockEnabled(false); @@ -259,7 +278,7 @@ public final class PinState { KbsValues kbsValues = SignalStore.kbsValues(); MasterKey masterKey = kbsValues.getOrCreateMasterKey(); - KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService(); + KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService(KbsEnclaves.current()); KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession(); HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession); KbsPinData kbsData = pinChangeSession.setPin(hashedPin, masterKey); @@ -272,6 +291,22 @@ public final class PinState { updateState(buildInferredStateFromOtherFields()); } + /** + * Should only be called by {@link org.thoughtcrime.securesms.jobs.KbsEnclaveMigrationWorkerJob}. + */ + @WorkerThread + public static synchronized void onMigrateToNewEnclave(@NonNull String pin) + throws IOException, UnauthenticatedResponseException + { + Log.i(TAG, "onMigrateToNewEnclave()"); + assertState(State.PIN_WITH_REGISTRATION_LOCK_DISABLED, State.PIN_WITH_REGISTRATION_LOCK_ENABLED); + + Log.i(TAG, "Migrating to enclave " + KbsEnclaves.current().getEnclaveName()); + setPinOnEnclave(KbsEnclaves.current(), pin, SignalStore.kbsValues().getOrCreateMasterKey()); + + ClearFallbackKbsEnclaveJob.clearAll(); + } + @WorkerThread private static void bestEffortRefreshAttributes() { Optional result = ApplicationDependencies.getJobManager().runSynchronously(new RefreshAttributesJob(), TimeUnit.SECONDS.toMillis(10)); @@ -301,23 +336,14 @@ public final class PinState { } @WorkerThread - private static void resetPinRetryCount(@NonNull Context context, @Nullable String pin, @NonNull KbsPinData kbsData) { + private static void resetPinRetryCount(@NonNull Context context, @Nullable String pin) { if (pin == null) { return; } - KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService(); - try { - KbsValues kbsValues = SignalStore.kbsValues(); - MasterKey masterKey = kbsValues.getOrCreateMasterKey(); - KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession(kbsData.getTokenResponse()); - HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession); - KbsPinData newData = pinChangeSession.setPin(hashedPin, masterKey); - - kbsValues.setKbsMasterKey(newData, pin); + setPinOnEnclave(KbsEnclaves.current(), pin, SignalStore.kbsValues().getOrCreateMasterKey()); TextSecurePreferences.clearRegistrationLockV1(context); - Log.i(TAG, "Pin set/attempts reset on KBS"); } catch (IOException e) { Log.w(TAG, "May have failed to reset pin attempts!", e); @@ -326,6 +352,20 @@ public final class PinState { } } + @WorkerThread + private static @NonNull KbsPinData setPinOnEnclave(@NonNull KbsEnclave enclave, @NonNull String pin, @NonNull MasterKey masterKey) + throws IOException, UnauthenticatedResponseException + { + KeyBackupService kbs = ApplicationDependencies.getKeyBackupService(enclave); + KeyBackupService.PinChangeSession pinChangeSession = kbs.newPinChangeSession(); + HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession); + KbsPinData newData = pinChangeSession.setPin(hashedPin, masterKey); + + SignalStore.kbsValues().setKbsMasterKey(newData, pin); + + return newData; + } + @WorkerThread private static void optOutOfPin() { SignalStore.kbsValues().optOut(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java index 9ee867d49..d97ad07dd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java @@ -102,8 +102,6 @@ public class Recipient { private final Capability groupsV2Capability; private final InsightsBannerTier insightsBannerTier; private final byte[] storageId; - private final byte[] identityKey; - private final VerifiedStatus identityStatus; private final MentionSetting mentionSetting; @@ -317,8 +315,6 @@ public class Recipient { this.uuidCapability = Capability.UNKNOWN; this.groupsV2Capability = Capability.UNKNOWN; this.storageId = null; - this.identityKey = null; - this.identityStatus = VerifiedStatus.DEFAULT; this.mentionSetting = MentionSetting.ALWAYS_NOTIFY; } @@ -361,8 +357,6 @@ public class Recipient { this.uuidCapability = details.uuidCapability; this.groupsV2Capability = details.groupsV2Capability; this.storageId = details.storageId; - this.identityKey = details.identityKey; - this.identityStatus = details.identityStatus; this.mentionSetting = details.mentionSetting; } @@ -782,14 +776,6 @@ public class Recipient { return storageId; } - public @NonNull VerifiedStatus getIdentityVerifiedStatus() { - return identityStatus; - } - - public @Nullable byte[] getIdentityKey() { - return identityKey; - } - public @NonNull UnidentifiedAccessMode getUnidentifiedAccessMode() { return unidentifiedAccessMode; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java index 010f12ce2..f23bac94b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java @@ -65,9 +65,7 @@ public class RecipientDetails { final Recipient.Capability uuidCapability; final Recipient.Capability groupsV2Capability; final InsightsBannerTier insightsBannerTier; - final byte[] storageId; - final byte[] identityKey; - final VerifiedStatus identityStatus; + final byte[] storageId; final MentionSetting mentionSetting; public RecipientDetails(@Nullable String name, @@ -113,8 +111,6 @@ public class RecipientDetails { this.groupsV2Capability = settings.getGroupsV2Capability(); this.insightsBannerTier = settings.getInsightsBannerTier(); this.storageId = settings.getStorageId(); - this.identityKey = settings.getIdentityKey(); - this.identityStatus = settings.getIdentityStatus(); this.mentionSetting = settings.getMentionSetting(); if (name == null) this.name = settings.getSystemDisplayName(); @@ -162,8 +158,6 @@ public class RecipientDetails { this.uuidCapability = Recipient.Capability.UNKNOWN; this.groupsV2Capability = Recipient.Capability.UNKNOWN; this.storageId = null; - this.identityKey = null; - this.identityStatus = VerifiedStatus.DEFAULT; this.mentionSetting = MentionSetting.ALWAYS_NOTIFY; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsDialogFragment.java index a587e5ceb..d28ae22e7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsDialogFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsDialogFragment.java @@ -71,9 +71,9 @@ public class CustomNotificationsDialogFragment extends DialogFragment { public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (ThemeUtil.isDarkTheme(requireActivity())) { - setStyle(STYLE_NO_FRAME, R.style.TextSecure_DarkTheme); + setStyle(STYLE_NO_FRAME, R.style.TextSecure_DarkTheme_AnimatedDialog); } else { - setStyle(STYLE_NO_FRAME, R.style.TextSecure_LightTheme); + setStyle(STYLE_NO_FRAME, R.style.TextSecure_LightTheme_AnimatedDialog); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/sharablegrouplink/ShareableGroupLinkDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/sharablegrouplink/ShareableGroupLinkDialogFragment.java index 81262831e..830ebdaf6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/sharablegrouplink/ShareableGroupLinkDialogFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/sharablegrouplink/ShareableGroupLinkDialogFragment.java @@ -10,6 +10,7 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.StringRes; import androidx.appcompat.widget.SwitchCompat; import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.DialogFragment; @@ -43,8 +44,8 @@ public final class ShareableGroupLinkDialogFragment extends DialogFragment { public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setStyle(STYLE_NO_FRAME, ThemeUtil.isDarkTheme(requireActivity()) ? R.style.TextSecure_DarkTheme - : R.style.TextSecure_LightTheme); + setStyle(STYLE_NO_FRAME, ThemeUtil.isDarkTheme(requireActivity()) ? R.style.TextSecure_DarkTheme_AnimatedDialog + : R.style.TextSecure_LightTheme_AnimatedDialog); } @Override @@ -101,16 +102,19 @@ public final class ShareableGroupLinkDialogFragment extends DialogFragment { shareRow.setOnClickListener(v -> GroupLinkBottomSheetDialogFragment.show(requireFragmentManager(), groupId)); - shareableGroupLinkRow.setOnClickListener(v -> viewModel.onToggleGroupLink(requireContext())); - approveNewMembersRow.setOnClickListener(v -> viewModel.onToggleApproveMembers(requireContext())); - resetLinkRow.setOnClickListener(v -> - new AlertDialog.Builder(requireContext()) - .setMessage(R.string.ShareableGroupLinkDialogFragment__are_you_sure_you_want_to_reset_the_group_link) - .setPositiveButton(R.string.ShareableGroupLinkDialogFragment__reset_link, (dialog, which) -> viewModel.onResetLink(requireContext())) - .setNegativeButton(android.R.string.cancel, null) - .show()); + viewModel.getCanEdit().observe(getViewLifecycleOwner(), canEdit -> { + if (canEdit) { + shareableGroupLinkRow.setOnClickListener(v -> viewModel.onToggleGroupLink()); + approveNewMembersRow.setOnClickListener(v -> viewModel.onToggleApproveMembers()); + resetLinkRow.setOnClickListener(v -> onResetGroupLink()); + } else { + shareableGroupLinkRow.setOnClickListener(v -> toast(R.string.ManageGroupActivity_only_admins_can_enable_or_disable_the_sharable_group_link)); + approveNewMembersRow.setOnClickListener(v -> toast(R.string.ManageGroupActivity_only_admins_can_enable_or_disable_the_option_to_approve_new_members)); + resetLinkRow.setOnClickListener(v -> toast(R.string.ManageGroupActivity_only_admins_can_reset_the_sharable_group_link)); + } + }); - viewModel.getToasts().observe(getViewLifecycleOwner(), t -> Toast.makeText(requireContext(), t, Toast.LENGTH_SHORT).show()); + viewModel.getToasts().observe(getViewLifecycleOwner(), this::toast); viewModel.getBusy().observe(getViewLifecycleOwner(), busy -> { if (busy) { @@ -126,6 +130,18 @@ public final class ShareableGroupLinkDialogFragment extends DialogFragment { }); } + private void onResetGroupLink() { + new AlertDialog.Builder(requireContext()) + .setMessage(R.string.ShareableGroupLinkDialogFragment__are_you_sure_you_want_to_reset_the_group_link) + .setPositiveButton(R.string.ShareableGroupLinkDialogFragment__reset_link, (dialog, which) -> viewModel.onResetLink()) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + + protected void toast(@StringRes int message) { + Toast.makeText(requireContext(), getString(message), Toast.LENGTH_SHORT).show(); + } + /** * Inserts zero width space characters between each character in the original ensuring it takes * the full width of the TextView. diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/sharablegrouplink/ShareableGroupLinkViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/sharablegrouplink/ShareableGroupLinkViewModel.java index c95be3d5e..dcc73a111 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/sharablegrouplink/ShareableGroupLinkViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/sharablegrouplink/ShareableGroupLinkViewModel.java @@ -1,14 +1,11 @@ package org.thoughtcrime.securesms.recipients.ui.sharablegrouplink; -import android.content.Context; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.lifecycle.LiveData; import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; -import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.LiveGroup; import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason; @@ -21,12 +18,16 @@ final class ShareableGroupLinkViewModel extends ViewModel { private final ShareableGroupLinkRepository repository; private final LiveData groupLink; - private final SingleLiveEvent toasts; + private final SingleLiveEvent toasts; private final SingleLiveEvent busy; + private final LiveData canEdit; private ShareableGroupLinkViewModel(@NonNull GroupId.V2 groupId, @NonNull ShareableGroupLinkRepository repository) { + LiveGroup liveGroup = new LiveGroup(groupId); + this.repository = repository; - this.groupLink = new LiveGroup(groupId).getGroupLink(); + this.groupLink = liveGroup.getGroupLink(); + this.canEdit = liveGroup.isSelfAdmin(); this.toasts = new SingleLiveEvent<>(); this.busy = new SingleLiveEvent<>(); } @@ -35,7 +36,7 @@ final class ShareableGroupLinkViewModel extends ViewModel { return groupLink; } - LiveData getToasts() { + LiveData getToasts() { return toasts; } @@ -43,7 +44,11 @@ final class ShareableGroupLinkViewModel extends ViewModel { return busy; } - void onToggleGroupLink(@NonNull Context context) { + LiveData getCanEdit() { + return canEdit; + } + + void onToggleGroupLink() { busy.setValue(true); repository.toggleGroupLinkEnabled(new AsynchronousCallback.WorkerThread() { @Override @@ -54,12 +59,12 @@ final class ShareableGroupLinkViewModel extends ViewModel { @Override public void onError(@Nullable GroupChangeFailureReason error) { busy.postValue(false); - toasts.postValue(context.getString(GroupErrors.getUserDisplayMessage(error))); + toasts.postValue(GroupErrors.getUserDisplayMessage(error)); } }); } - void onToggleApproveMembers(@NonNull Context context) { + void onToggleApproveMembers() { busy.setValue(true); repository.toggleGroupLinkApprovalRequired(new AsynchronousCallback.WorkerThread() { @Override @@ -70,24 +75,23 @@ final class ShareableGroupLinkViewModel extends ViewModel { @Override public void onError(@Nullable GroupChangeFailureReason error) { busy.postValue(false); - toasts.postValue(context.getString(GroupErrors.getUserDisplayMessage(error))); + toasts.postValue(GroupErrors.getUserDisplayMessage(error)); } }); } - void onResetLink(@NonNull Context context) { + void onResetLink() { busy.setValue(true); repository.cycleGroupLinkPassword(new AsynchronousCallback.WorkerThread() { @Override public void onComplete(@Nullable Void result) { busy.postValue(false); - toasts.postValue(context.getString(R.string.ShareableGroupLinkDialogFragment__group_link_reset)); } @Override public void onError(@Nullable GroupChangeFailureReason error) { busy.postValue(false); - toasts.postValue(context.getString(GroupErrors.getUserDisplayMessage(error))); + toasts.postValue(GroupErrors.getUserDisplayMessage(error)); } }); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterCodeFragment.java b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterCodeFragment.java index 5c6d93320..23337840b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterCodeFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterCodeFragment.java @@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.components.registration.CallMeCountDownView; import org.thoughtcrime.securesms.components.registration.VerificationCodeView; import org.thoughtcrime.securesms.components.registration.VerificationPinKeyboard; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.pin.PinRestoreRepository; import org.thoughtcrime.securesms.registration.ReceivedSmsEvent; import org.thoughtcrime.securesms.registration.service.CodeVerificationRequest; import org.thoughtcrime.securesms.registration.service.RegistrationCodeRequest; @@ -30,7 +31,6 @@ import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel; import org.thoughtcrime.securesms.util.CommunicationActions; import org.thoughtcrime.securesms.util.SupportEmailUtil; import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener; -import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse; import java.util.ArrayList; import java.util.Collections; @@ -107,7 +107,7 @@ public final class EnterCodeFragment extends BaseRegistrationFragment { RegistrationService registrationService = RegistrationService.getInstance(model.getNumber().getE164Number(), model.getRegistrationSecret()); - registrationService.verifyAccount(requireActivity(), model.getFcmToken(), code, null, null, null, + registrationService.verifyAccount(requireActivity(), model.getFcmToken(), code, null, null, new CodeVerificationRequest.VerifyCallback() { @Override @@ -133,10 +133,9 @@ public final class EnterCodeFragment extends BaseRegistrationFragment { } @Override - public void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull TokenResponse tokenResponse, @NonNull String kbsStorageCredentials) { + public void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull PinRestoreRepository.TokenData tokenData, @NonNull String kbsStorageCredentials) { model.setLockedTimeRemaining(timeRemaining); - model.setStorageCredentials(kbsStorageCredentials); - model.setKeyBackupCurrentToken(tokenResponse); + model.setKeyBackupTokenData(tokenData); keyboard.displayLocked().addListener(new AssertedSuccessListener() { @Override public void onSuccess(Boolean r) { @@ -147,7 +146,7 @@ public final class EnterCodeFragment extends BaseRegistrationFragment { } @Override - public void onIncorrectKbsRegistrationLockPin(@NonNull TokenResponse tokenResponse) { + public void onIncorrectKbsRegistrationLockPin(@NonNull PinRestoreRepository.TokenData tokenData) { throw new AssertionError("Unexpected, user has made no pin guesses"); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationLockFragment.java b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationLockFragment.java index 35e4b6559..c48cc6cf5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationLockFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationLockFragment.java @@ -25,12 +25,12 @@ import org.thoughtcrime.securesms.jobs.StorageAccountRestoreJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.lock.v2.PinKeyboardType; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.pin.PinRestoreRepository.TokenData; import org.thoughtcrime.securesms.registration.service.CodeVerificationRequest; import org.thoughtcrime.securesms.registration.service.RegistrationService; import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel; import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.concurrent.SimpleTask; -import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse; import java.util.concurrent.TimeUnit; @@ -106,10 +106,10 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment { getModel().getLockedTimeRemaining() .observe(getViewLifecycleOwner(), t -> timeRemaining = t); - TokenResponse keyBackupCurrentToken = getModel().getKeyBackupCurrentToken(); + TokenData keyBackupCurrentToken = getModel().getKeyBackupCurrentToken(); if (keyBackupCurrentToken != null) { - int triesRemaining = keyBackupCurrentToken.getTries(); + int triesRemaining = keyBackupCurrentToken.getTriesRemaining(); if (triesRemaining <= 3) { int daysRemaining = getLockoutDays(timeRemaining); @@ -158,8 +158,7 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment { RegistrationViewModel model = getModel(); RegistrationService registrationService = RegistrationService.getInstance(model.getNumber().getE164Number(), model.getRegistrationSecret()); - TokenResponse tokenResponse = model.getKeyBackupCurrentToken(); - String basicStorageCredentials = model.getBasicStorageCredentials(); + TokenData tokenData = model.getKeyBackupCurrentToken(); setSpinning(pinButton); @@ -167,8 +166,7 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment { model.getFcmToken(), model.getTextCodeEntered(), pin, - basicStorageCredentials, - tokenResponse, + tokenData, new CodeVerificationRequest.VerifyCallback() { @@ -189,19 +187,19 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment { } @Override - public void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull TokenResponse kbsTokenResponse, @NonNull String kbsStorageCredentials) { + public void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull TokenData kbsTokenData, @NonNull String kbsStorageCredentials) { throw new AssertionError("Not expected after a pin guess"); } @Override - public void onIncorrectKbsRegistrationLockPin(@NonNull TokenResponse tokenResponse) { + public void onIncorrectKbsRegistrationLockPin(@NonNull TokenData tokenData) { cancelSpinning(pinButton); pinEntry.getText().clear(); enableAndFocusPinEntry(); - model.setKeyBackupCurrentToken(tokenResponse); + model.setKeyBackupTokenData(tokenData); - int triesRemaining = tokenResponse.getTries(); + int triesRemaining = tokenData.getTriesRemaining(); if (triesRemaining == 0) { Log.w(TAG, "Account locked. User out of attempts on KBS."); diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/service/CodeVerificationRequest.java b/app/src/main/java/org/thoughtcrime/securesms/registration/service/CodeVerificationRequest.java index c6088ba43..2bebb0a1e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/service/CodeVerificationRequest.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/service/CodeVerificationRequest.java @@ -22,6 +22,8 @@ import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob; import org.thoughtcrime.securesms.jobs.RotateCertificateJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.pin.PinRestoreRepository; +import org.thoughtcrime.securesms.pin.PinRestoreRepository.TokenData; import org.thoughtcrime.securesms.pin.PinState; import org.thoughtcrime.securesms.push.AccountManagerFactory; import org.thoughtcrime.securesms.recipients.Recipient; @@ -65,41 +67,40 @@ public final class CodeVerificationRequest { /** * Asynchronously verify the account via the code. * - * @param fcmToken The FCM token for the device. - * @param code The code that was delivered to the user. - * @param pin The users registration pin. - * @param callback Exactly one method on this callback will be called. - * @param kbsTokenResponse By keeping the token, on failure, a newly returned token will be reused in subsequent pin - * attempts, preventing certain attacks, we can also track the attempts making missing replies easier to spot. + * @param fcmToken The FCM token for the device. + * @param code The code that was delivered to the user. + * @param pin The users registration pin. + * @param callback Exactly one method on this callback will be called. + * @param kbsTokenData By keeping the token, on failure, a newly returned token will be reused in subsequent pin + * attempts, preventing certain attacks, we can also track the attempts making missing replies easier to spot. */ static void verifyAccount(@NonNull Context context, @NonNull Credentials credentials, @Nullable String fcmToken, @NonNull String code, @Nullable String pin, - @Nullable String basicStorageCredentials, - @Nullable TokenResponse kbsTokenResponse, + @Nullable TokenData kbsTokenData, @NonNull VerifyCallback callback) { new AsyncTask() { private volatile LockedException lockedException; - private volatile TokenResponse kbsToken; + private volatile TokenData tokenData; @Override protected Result doInBackground(Void... voids) { final boolean pinSupplied = pin != null; - final boolean tryKbs = kbsTokenResponse != null; + final boolean tryKbs = tokenData != null; try { - kbsToken = kbsTokenResponse; - verifyAccount(context, credentials, code, pin, kbsTokenResponse, basicStorageCredentials, fcmToken); + this.tokenData = kbsTokenData; + verifyAccount(context, credentials, code, pin, tokenData, fcmToken); return Result.SUCCESS; } catch (KeyBackupSystemNoDataException e) { Log.w(TAG, "No data found on KBS"); return Result.KBS_ACCOUNT_LOCKED; } catch (KeyBackupSystemWrongPinException e) { - kbsToken = e.getTokenResponse(); + tokenData = TokenData.withResponse(tokenData, e.getTokenResponse()); return Result.KBS_WRONG_PIN; } catch (LockedException e) { if (pinSupplied && tryKbs) { @@ -110,8 +111,8 @@ public final class CodeVerificationRequest { lockedException = e; if (e.getBasicStorageCredentials() != null) { try { - kbsToken = getToken(e.getBasicStorageCredentials()); - if (kbsToken == null || kbsToken.getTries() == 0) { + tokenData = getToken(e.getBasicStorageCredentials()); + if (tokenData == null || tokenData.getTriesRemaining() == 0) { return Result.KBS_ACCOUNT_LOCKED; } } catch (IOException ex) { @@ -137,12 +138,12 @@ public final class CodeVerificationRequest { callback.onSuccessfulRegistration(); break; case PIN_LOCKED: - if (kbsToken != null) { + if (tokenData != null) { if (lockedException.getBasicStorageCredentials() == null) { throw new AssertionError("KBS Token set, but no storage credentials supplied."); } Log.w(TAG, "Reg Locked: V2 pin needed for registration"); - callback.onKbsRegistrationLockPinRequired(lockedException.getTimeRemaining(), kbsToken, lockedException.getBasicStorageCredentials()); + callback.onKbsRegistrationLockPinRequired(lockedException.getTimeRemaining(), tokenData, lockedException.getBasicStorageCredentials()); } else { Log.w(TAG, "Reg Locked: V1 pin needed for registration"); callback.onV1RegistrationLockPinRequiredOrIncorrect(lockedException.getTimeRemaining()); @@ -156,7 +157,7 @@ public final class CodeVerificationRequest { break; case KBS_WRONG_PIN: Log.w(TAG, "KBS Pin was wrong"); - callback.onIncorrectKbsRegistrationLockPin(kbsToken); + callback.onIncorrectKbsRegistrationLockPin(tokenData); break; case KBS_ACCOUNT_LOCKED: Log.w(TAG, "KBS Account is locked"); @@ -167,9 +168,9 @@ public final class CodeVerificationRequest { }.executeOnExecutor(SignalExecutors.UNBOUNDED); } - private static TokenResponse getToken(@Nullable String basicStorageCredentials) throws IOException { + private static TokenData getToken(@Nullable String basicStorageCredentials) throws IOException { if (basicStorageCredentials == null) return null; - return ApplicationDependencies.getKeyBackupService().getToken(basicStorageCredentials); + return new PinRestoreRepository().getTokenSync(basicStorageCredentials); } private static void handleSuccessfulRegistration(@NonNull Context context) { @@ -185,12 +186,11 @@ public final class CodeVerificationRequest { @NonNull Credentials credentials, @NonNull String code, @Nullable String pin, - @Nullable TokenResponse kbsTokenResponse, - @Nullable String kbsStorageCredentials, + @Nullable TokenData kbsTokenData, @Nullable String fcmToken) throws IOException, KeyBackupSystemWrongPinException, KeyBackupSystemNoDataException { - boolean isV2RegistrationLock = kbsTokenResponse != null; + boolean isV2RegistrationLock = kbsTokenData != null; int registrationId = KeyHelper.generateRegistrationId(false); boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(context); ProfileKey profileKey = findExistingProfileKey(context, credentials.getE164number()); @@ -206,7 +206,7 @@ public final class CodeVerificationRequest { SessionUtil.archiveAllSessions(context); SignalServiceAccountManager accountManager = AccountManagerFactory.createUnauthenticated(context, credentials.getE164number(), credentials.getPassword()); - KbsPinData kbsData = isV2RegistrationLock ? PinState.restoreMasterKey(pin, kbsStorageCredentials, kbsTokenResponse) : null; + KbsPinData kbsData = isV2RegistrationLock ? PinState.restoreMasterKey(pin, kbsTokenData.getEnclave(), kbsTokenData.getBasicAuth(), kbsTokenData.getTokenResponse()) : null; String registrationLockV2 = kbsData != null ? kbsData.getMasterKey().deriveRegistrationLock() : null; String registrationLockV1 = isV2RegistrationLock ? null : pin; boolean hasFcm = fcmToken != null; @@ -292,14 +292,14 @@ public final class CodeVerificationRequest { /** * The account is locked with a V2 (KBS) pin. Called before any user pin guesses. */ - void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull TokenResponse kbsTokenResponse, @NonNull String kbsStorageCredentials); + void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull TokenData kbsTokenData, @NonNull String kbsStorageCredentials); /** * The account is locked with a V2 (KBS) pin. Called after a user pin guess. *

* i.e. an attempt has likely been used. */ - void onIncorrectKbsRegistrationLockPin(@NonNull TokenResponse kbsTokenResponse); + void onIncorrectKbsRegistrationLockPin(@NonNull TokenData kbsTokenResponse); /** * V2 (KBS) pin is set, but there is no data on KBS. diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/service/RegistrationService.java b/app/src/main/java/org/thoughtcrime/securesms/registration/service/RegistrationService.java index 29ce4052b..d89a0f90f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/service/RegistrationService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/service/RegistrationService.java @@ -5,6 +5,7 @@ import android.app.Activity; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.thoughtcrime.securesms.pin.PinRestoreRepository; import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse; import java.io.IOException; @@ -39,10 +40,9 @@ public final class RegistrationService { @Nullable String fcmToken, @NonNull String code, @Nullable String pin, - @Nullable String basicStorageCredentials, - @Nullable TokenResponse tokenResponse, + @Nullable PinRestoreRepository.TokenData tokenData, @NonNull CodeVerificationRequest.VerifyCallback callback) { - CodeVerificationRequest.verifyAccount(activity, credentials, fcmToken, code, pin, basicStorageCredentials, tokenResponse, callback); + CodeVerificationRequest.verifyAccount(activity, credentials, fcmToken, code, pin, tokenData, callback); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java index c3894dea3..b851d95fb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java @@ -9,6 +9,7 @@ import androidx.lifecycle.SavedStateHandle; import androidx.lifecycle.ViewModel; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.pin.PinRestoreRepository.TokenData; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse; import org.whispersystems.signalservice.internal.util.JsonUtil; @@ -28,11 +29,10 @@ public final class RegistrationViewModel extends ViewModel { private final MutableLiveData textCodeEntered; private final MutableLiveData captchaToken; private final MutableLiveData fcmToken; - private final MutableLiveData basicStorageCredentials; private final MutableLiveData restoreFlowShown; private final MutableLiveData successfulCodeRequestAttempts; private final MutableLiveData requestLimiter; - private final MutableLiveData keyBackupCurrentTokenJson; + private final MutableLiveData kbsTokenData; private final MutableLiveData lockedTimeRemaining; private final MutableLiveData canCallAtTime; @@ -43,11 +43,10 @@ public final class RegistrationViewModel extends ViewModel { textCodeEntered = savedStateHandle.getLiveData("TEXT_CODE_ENTERED", ""); captchaToken = savedStateHandle.getLiveData("CAPTCHA"); fcmToken = savedStateHandle.getLiveData("FCM_TOKEN"); - basicStorageCredentials = savedStateHandle.getLiveData("BASIC_STORAGE_CREDENTIALS"); restoreFlowShown = savedStateHandle.getLiveData("RESTORE_FLOW_SHOWN", false); successfulCodeRequestAttempts = savedStateHandle.getLiveData("SUCCESSFUL_CODE_REQUEST_ATTEMPTS", 0); requestLimiter = savedStateHandle.getLiveData("REQUEST_RATE_LIMITER", new LocalCodeRequestRateLimiter(60_000)); - keyBackupCurrentTokenJson = savedStateHandle.getLiveData("KBS_TOKEN"); + kbsTokenData = savedStateHandle.getLiveData("KBS_TOKEN"); lockedTimeRemaining = savedStateHandle.getLiveData("TIME_REMAINING", 0L); canCallAtTime = savedStateHandle.getLiveData("CAN_CALL_AT_TIME", 0L); } @@ -158,28 +157,12 @@ public final class RegistrationViewModel extends ViewModel { requestLimiter.setValue(requestLimiter.getValue()); } - public void setStorageCredentials(@Nullable String storageCredentials) { - basicStorageCredentials.setValue(storageCredentials); + public @Nullable TokenData getKeyBackupCurrentToken() { + return kbsTokenData.getValue(); } - public @Nullable String getBasicStorageCredentials() { - return basicStorageCredentials.getValue(); - } - - public @Nullable TokenResponse getKeyBackupCurrentToken() { - String json = keyBackupCurrentTokenJson.getValue(); - if (json == null) return null; - try { - return JsonUtil.fromJson(json, TokenResponse.class); - } catch (IOException e) { - Log.w(TAG, e); - return null; - } - } - - public void setKeyBackupCurrentToken(TokenResponse tokenResponse) { - String json = tokenResponse == null ? null : JsonUtil.toJson(tokenResponse); - keyBackupCurrentTokenJson.setValue(json); + public void setKeyBackupTokenData(TokenData tokenData) { + kbsTokenData.setValue(tokenData); } public LiveData getLockedTimeRemaining() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/AccountConflictMerger.java b/app/src/main/java/org/thoughtcrime/securesms/storage/AccountConflictMerger.java index 277a5210c..8a5ed2494 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/AccountConflictMerger.java +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/AccountConflictMerger.java @@ -60,14 +60,15 @@ class AccountConflictMerger implements StorageSyncHelper.ConflictMerger inserts, @NonNull List deletes, @NonNull Optional accountUpdate, - @NonNull Optional accountInsert, - @NonNull Set archivedRecipients) + @NonNull Optional accountInsert) { int accountCount = Stream.of(currentLocalKeys) .filter(id -> id.getType() == ManifestRecord.Identifier.Type.ACCOUNT_VALUE) @@ -119,12 +119,12 @@ public final class StorageSyncHelper { Map storageKeyUpdates = new HashMap<>(); for (RecipientSettings insert : inserts) { - if (insert.getGroupType() == RecipientDatabase.GroupType.SIGNAL_V2 && insert.getGroupMasterKey() == null) { + if (insert.getGroupType() == RecipientDatabase.GroupType.SIGNAL_V2 && insert.getSyncExtras().getGroupMasterKey() == null) { Log.w(TAG, "Missing master key on gv2 recipient"); continue; } - storageInserts.add(StorageSyncModels.localToRemoteRecord(insert, archivedRecipients)); + storageInserts.add(StorageSyncModels.localToRemoteRecord(insert)); switch (insert.getGroupType()) { case NONE: @@ -173,7 +173,7 @@ public final class StorageSyncHelper { throw new AssertionError("Unsupported type!"); } - storageInserts.add(StorageSyncModels.localToRemoteRecord(update, newId.getRaw(), archivedRecipients)); + storageInserts.add(StorageSyncModels.localToRemoteRecord(update, newId.getRaw())); storageDeletes.add(ByteBuffer.wrap(oldId.getRaw())); completeIds.remove(oldId); completeIds.add(newId); @@ -330,21 +330,23 @@ public final class StorageSyncHelper { @NonNull List currentLocalStorageKeys, @NonNull MergeResult mergeResult) { - Set completeKeys = new HashSet<>(currentLocalStorageKeys); - - completeKeys.addAll(Stream.of(mergeResult.getAllNewRecords()).map(SignalRecord::getId).toList()); - completeKeys.removeAll(Stream.of(mergeResult.getAllRemovedRecords()).map(SignalRecord::getId).toList()); - - SignalStorageManifest manifest = new SignalStorageManifest(currentManifestVersion + 1, new ArrayList<>(completeKeys)); - List inserts = new ArrayList<>(); inserts.addAll(mergeResult.getRemoteInserts()); inserts.addAll(Stream.of(mergeResult.getRemoteUpdates()).map(RecordUpdate::getNew).toList()); - List deletes = Stream.of(mergeResult.getRemoteUpdates()).map(RecordUpdate::getOld).map(SignalStorageRecord::getId).map(StorageId::getRaw).toList(); - deletes.addAll(Stream.of(mergeResult.getRemoteDeletes()).map(SignalRecord::getId).map(StorageId::getRaw).toList()); + List deletes = new ArrayList<>(); + deletes.addAll(Stream.of(mergeResult.getRemoteDeletes()).map(SignalRecord::getId).toList()); + deletes.addAll(Stream.of(mergeResult.getRemoteUpdates()).map(RecordUpdate::getOld).map(SignalStorageRecord::getId).toList()); - return new WriteOperationResult(manifest, inserts, deletes); + Set completeKeys = new HashSet<>(currentLocalStorageKeys); + completeKeys.addAll(Stream.of(mergeResult.getAllNewRecords()).map(SignalRecord::getId).toList()); + completeKeys.removeAll(Stream.of(mergeResult.getAllRemovedRecords()).map(SignalRecord::getId).toList()); + completeKeys.addAll(Stream.of(inserts).map(SignalStorageRecord::getId).toList()); + completeKeys.removeAll(deletes); + + SignalStorageManifest manifest = new SignalStorageManifest(currentManifestVersion + 1, new ArrayList<>(completeKeys)); + + return new WriteOperationResult(manifest, inserts, Stream.of(deletes).map(StorageId::getRaw).toList()); } public static @NonNull byte[] generateKey() { @@ -413,12 +415,13 @@ public final class StorageSyncHelper { RecipientSettings settings = DatabaseFactory.getRecipientDatabase(context).getRecipientSettingsForSync(self.getId()); SignalAccountRecord account = new SignalAccountRecord.Builder(self.getStorageServiceId()) - .setUnknownFields(settings != null ? settings.getStorageProto() : null) + .setUnknownFields(settings != null ? settings.getSyncExtras().getStorageProto() : null) .setProfileKey(self.getProfileKey()) .setGivenName(self.getProfileName().getGivenName()) .setFamilyName(self.getProfileName().getFamilyName()) .setAvatarUrlPath(self.getProfileAvatar()) - .setNoteToSelfArchived(DatabaseFactory.getThreadDatabase(context).isArchived(self.getId())) + .setNoteToSelfArchived(settings != null && settings.getSyncExtras().isArchived()) + .setNoteToSelfForcedUnread(settings != null && settings.getSyncExtras().isForcedUnread()) .setTypingIndicatorsEnabled(TextSecurePreferences.isTypingIndicatorsEnabled(context)) .setReadReceiptsEnabled(TextSecurePreferences.isReadReceiptsEnabled(context)) .setSealedSenderIndicatorsEnabled(TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(context)) @@ -439,6 +442,15 @@ public final class StorageSyncHelper { } } + private static PhoneNumberPrivacyValues.PhoneNumberSharingMode remoteToLocalPhoneNumberSharingMode(AccountRecord.PhoneNumberSharingMode phoneNumberPhoneNumberSharingMode) { + switch (phoneNumberPhoneNumberSharingMode) { + case EVERYBODY : return PhoneNumberPrivacyValues.PhoneNumberSharingMode.EVERYONE; + case CONTACTS_ONLY: return PhoneNumberPrivacyValues.PhoneNumberSharingMode.CONTACTS; + case NOBODY : return PhoneNumberPrivacyValues.PhoneNumberSharingMode.NOBODY; + default : return PhoneNumberPrivacyValues.PhoneNumberSharingMode.CONTACTS; + } + } + public static void applyAccountStorageSyncUpdates(@NonNull Context context, Optional> update) { if (!update.isPresent()) { return; @@ -448,12 +460,13 @@ public final class StorageSyncHelper { public static void applyAccountStorageSyncUpdates(@NonNull Context context, @NonNull StorageId storageId, @NonNull SignalAccountRecord update, boolean fetchProfile) { DatabaseFactory.getRecipientDatabase(context).applyStorageSyncUpdates(storageId, update); - DatabaseFactory.getThreadDatabase(context).setArchived(Recipient.self().getId(), update.isNoteToSelfArchived()); TextSecurePreferences.setReadReceiptsEnabled(context, update.isReadReceiptsEnabled()); TextSecurePreferences.setTypingIndicatorsEnabled(context, update.isTypingIndicatorsEnabled()); TextSecurePreferences.setShowUnidentifiedDeliveryIndicatorsEnabled(context, update.isSealedSenderIndicatorsEnabled()); SignalStore.settings().setLinkPreviewsEnabled(update.isLinkPreviewsEnabled()); + SignalStore.phoneNumberPrivacy().setPhoneNumberListingMode(update.isPhoneNumberUnlisted() ? PhoneNumberPrivacyValues.PhoneNumberListingMode.UNLISTED : PhoneNumberPrivacyValues.PhoneNumberListingMode.LISTED); + SignalStore.phoneNumberPrivacy().setPhoneNumberSharingMode(remoteToLocalPhoneNumberSharingMode(update.getPhoneNumberSharingMode())); if (fetchProfile && update.getAvatarUrlPath().isPresent()) { ApplicationDependencies.getJobManager().add(new RetrieveProfileAvatarJob(Recipient.self(), update.getAvatarUrlPath().get())); diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncModels.java b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncModels.java index 6c43296b1..6084c91fa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncModels.java +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncModels.java @@ -20,42 +20,43 @@ public final class StorageSyncModels { private StorageSyncModels() {} - public static @NonNull SignalStorageRecord localToRemoteRecord(@NonNull RecipientSettings settings, @NonNull Set archived) { + public static @NonNull SignalStorageRecord localToRemoteRecord(@NonNull RecipientSettings settings) { if (settings.getStorageId() == null) { throw new AssertionError("Must have a storage key!"); } - return localToRemoteRecord(settings, settings.getStorageId(), archived); + return localToRemoteRecord(settings, settings.getStorageId()); } - public static @NonNull SignalStorageRecord localToRemoteRecord(@NonNull RecipientSettings settings, @NonNull byte[] rawStorageId, @NonNull Set archived) { + public static @NonNull SignalStorageRecord localToRemoteRecord(@NonNull RecipientSettings settings, @NonNull byte[] rawStorageId) { switch (settings.getGroupType()) { - case NONE: return SignalStorageRecord.forContact(localToRemoteContact(settings, rawStorageId, archived)); - case SIGNAL_V1: return SignalStorageRecord.forGroupV1(localToRemoteGroupV1(settings, rawStorageId, archived)); - case SIGNAL_V2: return SignalStorageRecord.forGroupV2(localToRemoteGroupV2(settings, rawStorageId, archived)); + case NONE: return SignalStorageRecord.forContact(localToRemoteContact(settings, rawStorageId)); + case SIGNAL_V1: return SignalStorageRecord.forGroupV1(localToRemoteGroupV1(settings, rawStorageId)); + case SIGNAL_V2: return SignalStorageRecord.forGroupV2(localToRemoteGroupV2(settings, rawStorageId)); default: throw new AssertionError("Unsupported type!"); } } - private static @NonNull SignalContactRecord localToRemoteContact(@NonNull RecipientSettings recipient, byte[] rawStorageId, @NonNull Set archived) { + private static @NonNull SignalContactRecord localToRemoteContact(@NonNull RecipientSettings recipient, byte[] rawStorageId) { if (recipient.getUuid() == null && recipient.getE164() == null) { throw new AssertionError("Must have either a UUID or a phone number!"); } return new SignalContactRecord.Builder(rawStorageId, new SignalServiceAddress(recipient.getUuid(), recipient.getE164())) - .setUnknownFields(recipient.getStorageProto()) + .setUnknownFields(recipient.getSyncExtras().getStorageProto()) .setProfileKey(recipient.getProfileKey()) .setGivenName(recipient.getProfileName().getGivenName()) .setFamilyName(recipient.getProfileName().getFamilyName()) .setBlocked(recipient.isBlocked()) .setProfileSharingEnabled(recipient.isProfileSharing() || recipient.getSystemContactUri() != null) - .setIdentityKey(recipient.getIdentityKey()) - .setIdentityState(localToRemoteIdentityState(recipient.getIdentityStatus())) - .setArchived(archived.contains(recipient.getId())) + .setIdentityKey(recipient.getSyncExtras().getIdentityKey()) + .setIdentityState(localToRemoteIdentityState(recipient.getSyncExtras().getIdentityStatus())) + .setArchived(recipient.getSyncExtras().isArchived()) + .setForcedUnread(recipient.getSyncExtras().isForcedUnread()) .build(); } - private static @NonNull SignalGroupV1Record localToRemoteGroupV1(@NonNull RecipientSettings recipient, byte[] rawStorageId, @NonNull Set archived) { + private static @NonNull SignalGroupV1Record localToRemoteGroupV1(@NonNull RecipientSettings recipient, byte[] rawStorageId) { GroupId groupId = recipient.getGroupId(); if (groupId == null) { @@ -67,14 +68,15 @@ public final class StorageSyncModels { } return new SignalGroupV1Record.Builder(rawStorageId, groupId.getDecodedId()) - .setUnknownFields(recipient.getStorageProto()) + .setUnknownFields(recipient.getSyncExtras().getStorageProto()) .setBlocked(recipient.isBlocked()) .setProfileSharingEnabled(recipient.isProfileSharing()) - .setArchived(archived.contains(recipient.getId())) + .setArchived(recipient.getSyncExtras().isArchived()) + .setForcedUnread(recipient.getSyncExtras().isForcedUnread()) .build(); } - private static @NonNull SignalGroupV2Record localToRemoteGroupV2(@NonNull RecipientSettings recipient, byte[] rawStorageId, @NonNull Set archived) { + private static @NonNull SignalGroupV2Record localToRemoteGroupV2(@NonNull RecipientSettings recipient, byte[] rawStorageId) { GroupId groupId = recipient.getGroupId(); if (groupId == null) { @@ -85,17 +87,18 @@ public final class StorageSyncModels { throw new AssertionError("Group is not V2"); } - GroupMasterKey groupMasterKey = recipient.getGroupMasterKey(); + GroupMasterKey groupMasterKey = recipient.getSyncExtras().getGroupMasterKey(); if (groupMasterKey == null) { throw new AssertionError("Group master key not on recipient record"); } return new SignalGroupV2Record.Builder(rawStorageId, groupMasterKey) - .setUnknownFields(recipient.getStorageProto()) + .setUnknownFields(recipient.getSyncExtras().getStorageProto()) .setBlocked(recipient.isBlocked()) .setProfileSharingEnabled(recipient.isProfileSharing()) - .setArchived(archived.contains(recipient.getId())) + .setArchived(recipient.getSyncExtras().isArchived()) + .setForcedUnread(recipient.getSyncExtras().isForcedUnread()) .build(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java b/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java index 511ae27df..4fb9460d0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java @@ -165,7 +165,7 @@ public class CommunicationActions { */ public static boolean handlePotentialGroupLinkUrl(@NonNull FragmentActivity activity, @NonNull String potentialGroupLinkUrl) { try { - GroupInviteLinkUrl groupInviteLinkUrl = GroupInviteLinkUrl.fromUrl(potentialGroupLinkUrl); + GroupInviteLinkUrl groupInviteLinkUrl = GroupInviteLinkUrl.fromUri(potentialGroupLinkUrl); if (groupInviteLinkUrl == null) { return false; diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java index 698ee1937..c5a87af8a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java @@ -26,6 +26,10 @@ public final class CursorUtil { return requireInt(cursor, column) != 0; } + public static byte[] requireBlob(@NonNull Cursor cursor, @NonNull String column) { + return cursor.getBlob(cursor.getColumnIndexOrThrow(column)); + } + public static Optional getString(@NonNull Cursor cursor, @NonNull String column) { if (cursor.getColumnIndex(column) < 0) { return Optional.absent(); @@ -41,4 +45,20 @@ public final class CursorUtil { return Optional.of(requireInt(cursor, column)); } } + + public static Optional getBoolean(@NonNull Cursor cursor, @NonNull String column) { + if (cursor.getColumnIndex(column) < 0) { + return Optional.absent(); + } else { + return Optional.of(requireBoolean(cursor, column)); + } + } + + public static Optional getBlob(@NonNull Cursor cursor, @NonNull String column) { + if (cursor.getColumnIndex(column) < 0) { + return Optional.absent(); + } else { + return Optional.fromNullable(requireBlob(cursor, column)); + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java index 7b407b809..645b5d392 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java @@ -48,7 +48,6 @@ public final class FeatureFlags { private static final long FETCH_INTERVAL = TimeUnit.HOURS.toMillis(2); private static final String USERNAMES = "android.usernames"; - private static final String REMOTE_DELETE = "android.remoteDelete"; private static final String GROUPS_V2_CREATE_VERSION = "android.groupsv2.createVersion"; private static final String GROUPS_V2_JOIN_VERSION = "android.groupsv2.joinVersion"; private static final String GROUPS_V2_LINKS_VERSION = "android.groupsv2.manageGroupLinksVersion"; @@ -66,7 +65,6 @@ public final class FeatureFlags { */ private static final Set REMOTE_CAPABLE = Sets.newHashSet( - REMOTE_DELETE, GROUPS_V2_CREATE_VERSION, GROUPS_V2_CAPACITY, GROUPS_V2_JOIN_VERSION, @@ -174,11 +172,6 @@ public final class FeatureFlags { return getBoolean(USERNAMES, false); } - /** Send support for remotely deleting a message. */ - public static boolean remoteDelete() { - return getBoolean(REMOTE_DELETE, false); - } - /** Attempt groups v2 creation. */ public static boolean groupsV2create() { return getVersionFlag(GROUPS_V2_CREATE_VERSION) == VersionFlag.ON && diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/GroupUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/GroupUtil.java index 93a7cc7ef..7a48332f7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/GroupUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/GroupUtil.java @@ -6,6 +6,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; +import org.signal.zkgroup.InvalidInputException; +import org.signal.zkgroup.groups.GroupMasterKey; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; @@ -66,6 +68,14 @@ public final class GroupUtil { return Optional.absent(); } + public static @NonNull GroupMasterKey requireMasterKey(@NonNull byte[] masterKey) { + try { + return new GroupMasterKey(masterKey); + } catch (InvalidInputException e) { + throw new AssertionError(e); + } + } + public static @NonNull GroupDescription getNonV2GroupDescription(@NonNull Context context, @Nullable String encodedGroup) { if (encodedGroup == null) { return new GroupDescription(context, null); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/SqlUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/SqlUtil.java index c0149dd01..b23240acb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/SqlUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/SqlUtil.java @@ -7,11 +7,13 @@ import androidx.annotation.NonNull; import net.sqlcipher.database.SQLiteDatabase; -import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; +import org.whispersystems.libsignal.util.guava.Preconditions; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -63,9 +65,9 @@ public final class SqlUtil { * change. In other words, if {@link SQLiteDatabase#update(String, ContentValues, String, String[])} * returns > 0, then you know something *actually* changed. */ - public static @NonNull UpdateQuery buildTrueUpdateQuery(@NonNull String selection, - @NonNull String[] args, - @NonNull ContentValues contentValues) + public static @NonNull Query buildTrueUpdateQuery(@NonNull String selection, + @NonNull String[] args, + @NonNull ContentValues contentValues) { StringBuilder qualifier = new StringBuilder(); Set> valueSet = contentValues.valueSet(); @@ -90,7 +92,29 @@ public final class SqlUtil { i++; } - return new UpdateQuery("(" + selection + ") AND (" + qualifier + ")", fullArgs.toArray(new String[0])); + return new Query("(" + selection + ") AND (" + qualifier + ")", fullArgs.toArray(new String[0])); + } + + public static @NonNull Query buildCollectionQuery(@NonNull String column, @NonNull Collection values) { + Preconditions.checkArgument(values.size() > 0); + + StringBuilder query = new StringBuilder(); + Object[] args = new Object[values.size()]; + + int i = 0; + + for (Object value : values) { + query.append("?"); + args[i] = value; + + if (i != values.size() - 1) { + query.append(", "); + } + + i++; + } + + return new Query(column + " IN (" + query.toString() + ")", buildArgs(args)); } public static String[] appendArg(@NonNull String[] args, String addition) { @@ -102,11 +126,11 @@ public final class SqlUtil { return output; } - public static class UpdateQuery { + public static class Query { private final String where; private final String[] whereArgs; - private UpdateQuery(@NonNull String where, @NonNull String[] whereArgs) { + private Query(@NonNull String where, @NonNull String[] whereArgs) { this.where = where; this.whereArgs = whereArgs; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/Util.java b/app/src/main/java/org/thoughtcrime/securesms/util/Util.java index 86b0b444d..45895e1d4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/Util.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/Util.java @@ -116,6 +116,18 @@ public class Util { return join(boxed, delimeter); } + @SafeVarargs + public static @NonNull List join(@NonNull List... lists) { + int totalSize = Stream.of(lists).reduce(0, (sum, list) -> sum + list.size()); + List joined = new ArrayList<>(totalSize); + + for (List list : lists) { + joined.addAll(list); + } + + return joined; + } + public static String join(List list, String delimeter) { StringBuilder sb = new StringBuilder(); diff --git a/app/src/main/proto/Database.proto b/app/src/main/proto/Database.proto index df6edc9d5..ad1937d18 100644 --- a/app/src/main/proto/Database.proto +++ b/app/src/main/proto/Database.proto @@ -28,9 +28,10 @@ import "SignalService.proto"; import "DecryptedGroups.proto"; message DecryptedGroupV2Context { - signalservice.GroupContextV2 context = 1; - DecryptedGroupChange change = 2; - DecryptedGroup groupState = 3; + signalservice.GroupContextV2 context = 1; + DecryptedGroupChange change = 2; + DecryptedGroup groupState = 3; + DecryptedGroup previousGroupState = 4; } message TemporalAuthCredentialResponse { diff --git a/app/src/main/res/layout/group_new_candidate_recipient_list_item.xml b/app/src/main/res/layout/group_new_candidate_recipient_list_item.xml index 64af41b64..a5ecc2cc2 100644 --- a/app/src/main/res/layout/group_new_candidate_recipient_list_item.xml +++ b/app/src/main/res/layout/group_new_candidate_recipient_list_item.xml @@ -17,7 +17,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> - - - حذف الرسائل جارٍ… إحذف بالنسبة لي إحذف بالنسبة للجميع - سيتم حذف هذه الرسالة لجميع الأعضاء في هذه المحادثة. الجميع سيرى أنك حذفت رسالة. + لم يتم العثور على الرسالة الأصلية الرسالة الأصلية لم تعد متوفرة فشل في فتح الرسالة diff --git a/app/src/main/res/values-bs/strings.xml b/app/src/main/res/values-bs/strings.xml index 0eaaba900..b2279d910 100644 --- a/app/src/main/res/values-bs/strings.xml +++ b/app/src/main/res/values-bs/strings.xml @@ -131,8 +131,11 @@ Ukloni sliku grupe Ažurirajte Signal + Ova verzija aplikacije više nije podržana. Da biste mogli slati i primati poruke, instalirajte zadnju verziju. Ažuriraj + Ne ažuriraj Upozorenje + Vaša verzija Signala je istekla. Možete pristupiti ranijim porukama, ali nećete moći slati ni primati nove poruke dok ne ažurirate aplikaciju. Nije pronađen internet-preglednik. Nije pronađena aplikacija za email. @@ -293,7 +296,7 @@ Brišem poruke… Izbriši za mene Izbriši za svakoga - Ova će poruka biti bespovratno izbrisana za sve učesnike u konverzaciji. Oni će moći vidjeti da ste Vi izbrisali poruku. + Originalna poruka nije pronađena Originalna poruka više nije dostupna Neuspjelo otvaranje poruke @@ -385,6 +388,8 @@ Optimizuj za nepostojanje Play Services Ovaj uređaj ne podržava Play Services. Pritisnite ovdje kako biste onemogućili sistemske optimizacije korištenja baterije koje sprečavaju Signal da prima poruke dok je aplikacija zatvorena. + Ova verzija Signala je istekla. Ažurirajte aplikaciju sada da biste mogli slati i primati poruke. + Ažuriraj sada Dijeli sa Višestruki prilozi podržani su samo za slike i videozapise @@ -519,6 +524,8 @@ Zahtjevi za članstvo i pozivnice Dodajte članove Izmijeni informacije o grupi + Ko može uvrstiti nove članove? + Ko može mijenjati informacije o ovoj grupi? Link grupe Blokiraj grupu Prestani s blokiranjem grupe @@ -550,6 +557,9 @@ Neuspjelo ažuriranje grupe usljed greške u mreži, molimo, pokušajte kasnije Upišite ime i dodajte sliku Stara grupa + Ovo je stara grupa. Opcije poput grupnog administratora dostupne su samo za nove grupe. + Ovo je nezaštićena MMS grupa. Da biste razgovarali u privatnosti i imali pristup opcijama poput naziva grupe, pozovite svoje kontakte da koriste Signal. + Pozovi odmah Obavijesti me kad me neko spomene Primiti obavještenja kad Vas neko spomene u prigušenim konverzacijama? @@ -646,6 +656,7 @@ Pristupanje grupi putem linka nije još podržano u Signalu. To će biti moguće u jednoj od narednih verzija. Verzija Signala koju koristite ne podržava linkove za grupu. Instalirajte zadnju verziju da biste se mogli priključiti ovoj grupi putem linka. Ažurirajte Signal + Jedan ili više Vaših povezanih uređaja koriste verziju Signala koja ne podržava linkove za grupe. Ažurirajte Signal na svojim povezanim uređajima da biste pristupili ovoj grupi. Link za grupu nije ispravan Uvrstiti “%1$s” u grupu? @@ -985,6 +996,13 @@ Vaše poruke neće isteći. Poslane i primljene poruke u ovoj konverzaciji nestat će %s nakon što su viđene. + Ažuriraj sada + Ova verzija Signala ističe danas. Ažurirajte aplikaciju na posljednju verziju. + + Ova verzija Signala ističe sutra. Ažurirajte aplikaciju na posljednju verziju. + Ova verzija Signala ističe za %d dana. Ažurirajte aplikaciju na posljednju verziju. + Ova verzija Signala ističe za %d dana. Ažurirajte aplikaciju na posljednju verziju. + Unesite lozinku Signal sličica @@ -1063,10 +1081,20 @@ Da biste nazvali %1$s, Signalu je potreban pristup Vašoj kameri Signal %1$s Pozivam… + Grupni poziv Signal glasovni poziv… Signal videopoziv… + Nazovi + Grupni poziv + Vidi učesnike + Vaš videoprikaz je isključen + + U ovom pozivu · %1$d osoba + U ovom pozivu · %1$d osobe + U ovom pozivu · %1$d osoba + Odaberite svoju zemlju Morate precizirati @@ -2140,9 +2168,15 @@ Podsjetit ćemo Vas kasnije. Kreiranje PIN-a postat će obavezno za %1$d dana. Podsjetit ćemo Vas kasnije. Potvrda Vašeg PIN-a postat će obavezna za %1$d dana. + Kažite Signalu svoje utiske + Voljeli bismo čuti Vaše utiske i prijedloge kako bismo Signal učinili najboljom aplikacijom za komunikaciju na planetu. Saznajte više Odbaci + Signal istraživanje + Mi vjerujemo u privatnost.

Signal Vas ne prati, niti prikuplja Vaše podatke. Da bismo unaprijedili Signal, oslanjamo se na utiske i sugestije korisnika, i voljeli bismo čuti Vaše.

Pokrenuli smo anketu s ciljem da razumijemo kako Vi koristite Signal. Naša anketa ne pohranjuje bilo kakve podatke na temelju kojih biste bili identifikovani. Ako želite s nama podijeliti dodatna zapažanja, imat ćete mogućnost da ostavite kontakt-informacije.

Ako možete izdvojiti nekoliko minuta i kazati nam svoje utiske, bit ćemo sretni da ih saslušamo.

]]>
+ Započni anketu Ne, hvala + Anketa se sprovodi putem Surveygizma na sigurnoj domeni surveys.signalusers.org Sličica transporta Učitavanje… @@ -2282,6 +2316,8 @@ Nezaštićen poziv Videopoziv Smijeniti %1$s kao administratora grupe? + \"%1$s\" će moći uređivati ovu grupu i njene članove. + Udaljiti %1$s iz grupe? Ukloni Kopirano u međuspremnik Administrator @@ -2291,6 +2327,9 @@ Stare spram novih grupa Šta su stare gurpe? Stare su one grupe koje nisu kompatibilne s novim grupnim opcijama poput administratora i detaljnih grupnih izvještaja. + Mogu li nadograditi staru grupu? + Stare grupe ne mogu se još uvijek nadograditi u nove, ali možete oformiti novu grupu sa istim članovima, ako oni koriste posljednju verziju Signala. + Signal će u budućnosti nuditi mogućnost za nadogradnju starih grupa. Podijeli preko Signala Kopiraj diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 8a01a0b3c..a73e6a704 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -128,8 +128,11 @@ Voleu suprimir la foto del grup? Actualitza el Signal - Actualització + Aquesta versió de l\'aplicació ja no té assistència. Per continuar enviant i rebent missatges, actualitzeu-la a la darrera versió. + Actualitza-la + No l\'actualitzis Advertiment + La versió del Signal ha vençut. Podeu veure l\'historial de missatges però no podreu enviar-ne ni rebre\'n fins que l\'actualitzeu. No s\'ha trobat cap navegador web. No s\'ha trobat cap aplicació de correu. @@ -283,7 +286,7 @@ Se suprimeixen missatges… Suprimeix per a mi Suprimeix per a tothom - Aquest missatge se suprimirà permanentment per a tothom que sigui a la conversa. Els membres podran veure que heu suprimit un missatge. + No s\'ha trobat el missatge original. El missatge original ja no està disponible. Ha fallat obrir el missatge. @@ -371,6 +374,8 @@ Optimitza per a la no presència dels Play Services El dispositiu no és compatible amb Play Services. Toqueu per desactivar les optimitzacions de la bateria que impedeixen que el Signal rebi missatges quan estigui inactiu. + Aquesta versió del Signal ha vençut. Actualitzeu-la ara per enviar i rebre missatges. + Actualitza-la ara Comparteix amb Els adjunts múltiples només s\'admeten per a imatges i vídeos. @@ -498,6 +503,8 @@ Sol·licituds d\'adhesió i invitacions Afegiu-hi membres Edita la informació del grup + Qui pot afegir membres nous? + Qui pot editar la informació del grup? Enllaç del grup Bloca el grup Desbloca el grup @@ -527,6 +534,9 @@ Ha fallat actualitzar el grup a causa d\'un error de xarxa. Torneu-ho a provar més tard. Editeu el nom i la imatge Grup de llegat + Aquest és un grup de llegat. Característiques com ara els administradors de grup només estan disponibles per als grups nous. + Aquest és un grup d\'MMS no segur. Per conversar privadament i accedir a funcions com ara els noms de grup, convideu els contactes al Signal. + Convida ara Notifica\'m les mencions Voleu rebre notificacions quan us mencionin en converses silenciades? @@ -619,6 +629,7 @@ Afegir-se a un grup per un enllaç encara no és possible amb el Signal. Aquesta funció s\'activarà en una actualització propera. La versió del Signal que useu no admet enllaços de grup. Actualitzeu-lo a la versió més recent per afegir-vos a aquest grup per l\'enllaç. Actualitza el Signal + Un o més dels vostres dispositius enllaçats executen una versió del Signal que no admet enllaços de grup. Actualitzeu el Signal dels dispositius enllaçats per afegir-vos a aquest grup. L\'enllaç de grup no és vàlid. Voleu afegir %1$s al grup? @@ -939,9 +950,15 @@ No es pot escanejar un codi QR sense permís de la càmera. Missatges efímers - El missatges no expiraran. + El missatges no venceran. Els missatges enviats i rebuts a aquesta conversa desapareixeran %s després que s\'hagin llegit. + Actualitza-la ara + La vostra versió del Signal vencerà avui. Actualitzeu-la a la versió més recent. + + Aquesta versió del Signal vencerà demà. Actualitzeu-la a la versió més recent. + Aquesta versió del Signal vencerà d\'aquí a %d dies. Actualitzeu-la a la versió més recent. + Escriviu la contrasenya Icona del Signal @@ -1019,10 +1036,19 @@ Per trucar a %1$s, el Signal necessita accés a la càmera. Trucada del Signal: %1$s Es truca… + Trucada de grup Trucada de veu del Signal… Trucada de vídeo del Signal… + Inicia una trucada + Trucada de grup + Mostra els participants + El vídeo està desactivat. + + En aquesta trucada · %1$d persona + En aquesta trucada · %1$d persones + Seleccioneu el país Heu d\'especificar el @@ -1868,7 +1894,7 @@ S\'ha rebut un missatge d\'intercanvi de claus per a una versió del protocol no Missatges efímers - Missatges que caduquen + Missatges que vencen Convida @@ -2074,9 +2100,11 @@ S\'ha rebut un missatge d\'intercanvi de claus per a una versió del protocol no Us ho recordarem més tard. Confirmar el PIN serà obligatori d\'aquí a %1$d dies. Expliqueu a Signal què en penseu + Per fer del Signal la millor aplicació de missatgeria del planeta, ens agradaria rebre\'n els vostres comentaris. Més informació Descarta Recerca del Signal + Creiem en la privadesa.

El Signal no us fa un seguiment ni recopila les vostres dades. Per millorar el Signal per a tothom, confiem en els comentaris dels usuaris i ens encantaria rebre el vostre.

Estem fent una enquesta per entendre com useu el Signal. La nostra enquesta no recull cap dada que us identifiqui. Si esteu interessats a compartir comentaris addicionals, podreu proporcionar la informació de contacte.

Si teniu uns quants minuts i comentaris a oferir, ens encantaria rebre notícies vostres.

]]>
Feu l\'enquesta No, gràcies. Surveygizmo allotja l\'enquesta al domini segur surveys.signalusers.org. @@ -2219,6 +2247,8 @@ S\'ha rebut un missatge d\'intercanvi de claus per a una versió del protocol no Trucada de veu no segura Trucada de vídeo Voleu suprimir %1$s com a administrador del grup? + %1$s podrà editar aquest grup i els seus membres. + Voleu suprimir %1$s d\'aquest grup? Suprimeix Copiat al porta-retalls Administrador @@ -2229,6 +2259,8 @@ S\'ha rebut un missatge d\'intercanvi de claus per a una versió del protocol no Què són els grups de llegat? Els grups de llegat són grups que no són compatibles amb les funcions dels grups nous com ara els administradors i les actualitzacions més descriptives. Puc actualitzar un grup de llegat? + Els grups de llegat no es poden actualitzar a grups nous, però podeu crear un grup nou amb els mateixos membres sempre que tinguin la darrera versió del Signal. + El Signal oferirà una manera d\'actualitzar els grups de llegat més endavant. Comparteix per Signal Copia diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index d6497723e..ac7523086 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -303,7 +303,7 @@ Mažu zprávy… Smazat pro mne Smazat pro všechny - Tato zpráva bude trvale smazána pro všechny členy konverzace. Členové konverzace budou moci vidět, ze jste zprávu smazali. + Původní zpráva nebyla nalezena Původní zpráva již není dostupná Zprávu se nepovedlo otevřít diff --git a/app/src/main/res/values-cy/strings.xml b/app/src/main/res/values-cy/strings.xml index 964685591..f7214fe3c 100644 --- a/app/src/main/res/values-cy/strings.xml +++ b/app/src/main/res/values-cy/strings.xml @@ -134,8 +134,11 @@ Tynnu llun y grŵp? Diweddaru Signal + Nid yw\'r fersiwn hon o\'r ap yn cael ei chefnogi mwyach. I barhau i anfon a derbyn negeseuon, diweddarwch nhw i\'r fersiwn ddiweddaraf. Diweddaru + Peidio â Diweddaru Rhybudd + Mae eich fersiwn o Signal wedi dod i ben. Gallwch weld hanes eich neges ond ni fyddwch yn gallu anfon na derbyn negeseuon nes i chi ddiweddaru. Heb ganfod porwr gwe. Heb ganfod ap e-bost. @@ -305,7 +308,7 @@ Send neges heb ei ddiogelu?
Dileu negeseuon… Dileu i mi Dileu i bawb - Bydd y neges hon yn cael ei dileu yn barhaol i bawb sydd yn y sgwrs. Bydd aelodau\'n gallu gweld eich bod wedi dileu y neges. + Heb ganfod y neges wreiddiol Nid yw\'r neges wreiddiol bellach ar gael Methu agor neges @@ -401,6 +404,8 @@ Send neges heb ei ddiogelu? Optimeiddio ar gyfer Play Services coll Nid yw\'r ddyfais hon yn cefnogi Play Services Tapiwch i analluogi gwelliannau batri system sy\'n atal Signal rhag adfer negeseuon tra\'n anweithredol. + Mae\'r fersiwn hon o Signal wedi dod i ben. Diweddarwch nawr i anfon a derbyn negeseuon. + Diweddaru nawr Rhannu â Dim ond ar gyfer delweddau a fideos y mae atodiadau lluosog yn cael eu cynnal @@ -542,6 +547,8 @@ Send neges heb ei ddiogelu? Ceisiadau aelodau a gwahoddiadau Ychwanegu aelodau Golygu manylion y grŵp + Pwy all ychwanegu aelodau newydd? + Pwy all olygu manylion y grŵp hwn? Dolen grŵp Rhwystro\'r grŵp Dad-rwystro grŵp @@ -575,6 +582,9 @@ Send neges heb ei ddiogelu? Wedi methu diweddaru\'r grŵp oherwydd gwall rhwydwaith, ceisiwch eto yn nes ymlaen Golygu enw a llun Hen Grŵp + Hen Grŵp yw hwn. Dim ond ar gyfer Grwpiau Newydd y mae nodweddion fel gweinyddwyr grwpiau ar gael. + Mae hwn yn Grŵp MMS anniogel. I sgwrsio\'n breifat a chael nodweddion fel enwau grwpiau, gwahoddwch eich cysylltiadau i Signal. + Gwahodd nawr Rhowch wybod i mi am Grybwylliadau Derbyn hysbysiadau pan soniwyd amdanoch mewn sgyrsiau tawel? @@ -675,6 +685,7 @@ Send neges heb ei ddiogelu? Nid yw Signal yn cefnogi ymuno â grŵp trwy ddolen eto. Bydd y nodwedd hon yn cael ei rhyddhau mewn diweddariad sydd ar ddod. Nid yw\'r fersiwn o Signal rydych chi\'n ei defnyddio yn cefnogi cysylltiadau grŵp. Diweddariad i\'r fersiwn ddiweddaraf i ymuno â\'r grŵp hwn trwy ddolen. Diweddaru Signal + Mae un neu fwy o\'ch dyfeisiau cysylltiedig yn rhedeg fersiwn o Signal nad yw\'n cefnogi cysylltiadau grŵp. Diweddarwch y Signal ar eich dyfais\dyfeisiau cysylltiedig i ymuno â\'r grŵp hwn. Nid yw\'r cyswllt grŵp yn ddilys Ychwanegu “%1$s” i\'r grŵp? @@ -1029,6 +1040,14 @@ Send neges heb ei ddiogelu? Ni fydd eich negeseuon yn dod i ben. Bydd negeseuon sy\'n cael eu anfon a\'u derbyn yn y sgwrs yn diflannu %s ar ôl iddynt gael eu gweld. + Diweddaru nawr + Bydd y fersiwn hon o Signal yn dod i ben heddiw. Diweddarwch i\'r fersiwn ddiweddaraf. + + Bydd y fersiwn hon o Signal yn dod i ben yfory. Diweddarwch i\'r fersiwn ddiweddaraf. + Bydd y fersiwn hon o Signal yn dod i ben mewn% d diwrnod. Diweddarwch i\'r fersiwn ddiweddaraf. + Bydd y fersiwn hon o Signal yn dod i ben mewn% d diwrnod. Diweddarwch i\'r fersiwn ddiweddaraf. + Bydd y fersiwn hon o Signal yn dod i ben mewn% d diwrnod. Diweddarwch i\'r fersiwn ddiweddaraf. + Rhoi cyfrinymadrodd Eicon Signal @@ -1108,10 +1127,21 @@ Send neges heb ei ddiogelu? I alw %1$s, mae angen i Signal gael mynediad i\'ch camera. Signal %1$s Yn galw… + Galwad Grŵp Galwad llais Signal… Galwad fideo Signal + Cychwyn Galwad + Galwad Grŵp + Gweld cyfranogwyr + Mae\'ch fideo i ffwrdd + + Yn yr alwad hon · %1$d person + Yn yr alwad hon · %1$d o bobl + Yn yr alwad hon · %1$d o bobl + Yn yr alwad hon · %1$d o bobl + Dewiswch eich gwlad Rhaid i chi roi @@ -2204,9 +2234,11 @@ Send neges heb ei ddiogelu? Byddwn yn eich atgoffa\'n ddiweddarach. Bydd creu PIN yn dod yn orfodol ymhen %1$d diwrnod. Dywedwch wrth Signal beth you eich barn + I wneud Signal yr ap negeseuon gorau ar y blaned, byddem wrth ein bodd yn clywed eich adborth. Dysgu rhagor Diddymu Ymchwil Signal + Rydym yn credu mewn preifatrwydd.

Nid yw Signal yn eich tracio nac yn casglu\'ch data. Er mwyn gwella Signal i bawb, rydym yn dibynnu ar adborth defnyddwyr, a byddem wrth ein bodd â\'ch adborth chi.

Rydyn ni\'n cynnal arolwg i ddeall sut rydych chi\'n defnyddio Signal. Nid yw ein harolwg yn casglu unrhyw ddata a fydd yn eich adnabod chi. Os oes gennych ddiddordeb mewn rhannu adborth ychwanegol, bydd gennych yr opsiwn i ddarparu gwybodaeth gyswllt.

Os oes gennych ychydig funudau ac adborth i\'w gynnig, byddem wrth ein bodd yn clywed gennych.

]]>
Cymryd yr Arolwg Dim diolch Surveygizmo sydd yn cynnal yr arolwg yn y parth diogel surveys.signalusers.org @@ -2349,6 +2381,8 @@ Send neges heb ei ddiogelu? Galwad llais anniogel Galwad fideo Tynnu %1$s fel gweinyddwr grŵp? + Bydd \"%1$s\" yn gallu golygu\'r grŵp hwn a\'i aelodau + Tynnu %1$s o\'r grŵp hwn? Tynnu Wedi\'i gopïo i\'r clipfwrdd Gweinyddwr @@ -2359,6 +2393,8 @@ Send neges heb ei ddiogelu? Beth yw Hen Grwpiau? Mae Hen Grwpiau yn grwpiau sydd ddim yn gydnaws â nodweddion Grŵp Newydd fel gweinyddwyr a diweddariadau grwpiau mwy disgrifiadol. Gai “uwchraddio” Hen Grŵp? + Nid oes modd uwchraddio Hen Grwpiau i Grwpiau Newydd eto, ond gallwch greu Grŵp Newydd gyda\'r un aelodau os ydyn nhw ar y fersiwn ddiweddaraf o Signal. + Bydd Signal yn cynnig ffordd i uwchraddio Hen Etifeddiaeth yn y dyfodol. Rhannu trwy Signal Copïo diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index e3d3a6ca3..8a540fac2 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -128,11 +128,11 @@ Fjern gruppefoto? Opdatér Signal - Denne version af appen understøttes ikke længere. Opdater til den nyeste version for at fortsætte med at sende og modtage beskeder. + Denne version af app´en understøttes ikke længere. Opdatér til den nyeste version for at fortsætte med at sende og modtage beskeder Opdatér Opdatér ikke Advarsel - Din version af Signal er udløbet. Du kan se din beskedhistorik, men du kan ikke sende eller modtage beskeder, før du opdaterer. + Din version af Signal er udløbet. Du kan se din beskedhistorik, men du kan ikke sende eller modtage beskeder, før du opdaterer Ingen Webbrowser fundet Ingen email app fundet @@ -287,7 +287,7 @@ Sletter beskeder… Slet for mig Slet for alle - Denne meddelelse slettes permanent for alle i samtalen. Medlemmerne kan se at du har slettet en besked + Original besked blev ikke fundet Original besked er ikke længere tilgængelig Fejl ved åbning af besked @@ -375,7 +375,7 @@ Optimér for manglende Play Services Enheden understøtter ikke Play Services. Tap for at slå batterioptimering fra, som forhindrer Signal i at modtage beskeder i baggrunden - Denne version af Signal er udløbet. Opdater nu for at sende og modtage beskeder + Denne version af Signal er udløbet. Opdatér nu for at sende og modtage beskeder Opdatér nu Del med @@ -423,7 +423,7 @@ Disse brugere kan ikke automatisk tilføjes gruppen af dig. De er blevet inviteret til at deltage, og kan ikke se gruppebeskeder før de accepterer Forlad gruppe? - Du vil ikke længere kunde sende eller modtage beskeder i gruppen + Du vil ikke længere kunne sende eller modtage beskeder i gruppen Forlad Vælg ny administrator Før du forlader gruppen, skal du vælge mindst én ny administrator @@ -505,7 +505,7 @@ Tilføj medlemmer Rediger gruppeinformation Hvem kan tilføje nye medlemmer? - Hvem kan redigere denne gruppes information? + Hvem kan redigere gruppens information? Gruppelink Blokér gruppe Ophæv blokering af gruppe @@ -535,8 +535,8 @@ Gruppen kunne ikke opdateres på grund af en netværksfejl. Prøv igen senere Redigér navn og billede Forældet gruppe - Dette er en forældret gruppe. Funktioner som gruppeadministratorer er kun tilgængelige for nye grupper. - Dette er en usikker MMS-gruppe. For at chatte privat og få adgang til funktioner som gruppenavne skal du invitere dine kontakter til Signal. + Dette er en forældret gruppe. Funktioner som gruppeadministratorer, er kun tilgængelige for nye grupper + Dette er en usikker MMS-gruppe. For at chatte privat og få adgang til funktioner som gruppenavne, skal du invitere dine kontakter til Signal Invitér nu Underret mig ved omtaler @@ -630,7 +630,7 @@ Deltagelse i en gruppe via et link understøttes endnu ikke af Signal. Funktionen frigives i en kommende opdatering Signal-versionen som du anvender, understøtter ikke gruppelinks. Opdater til den nyeste version for at deltage i denne gruppe via link Opdatér Signal - En eller flere af din(e) forbundne enhed(er) kører en version af Signal, der ikke understøtter gruppelinks. Opdater Signal for at deltage i denne gruppe. + En eller flere af din(e) forbundne enhed(er) kører en version af Signal, der ikke understøtter gruppelinks. Opdatér Signal for at deltage i gruppen Gruppelink er ikke gyldigt Føj \"%1$s\" til gruppen? @@ -956,10 +956,10 @@ Beskeder sendt eller modtaget i denne samtale, vil udløbe %s efter de er læst Opdatér nu - Denne version af Signal udløber i dag. Opdater til den nyeste version. + Denne version af Signal udløber i dag. Opdatér til den nyeste version Denne version af Signal udløber i morgen. Opdater til den nyeste version. - Denne version af Signal udløber om %d dage. Opdater til den nyeste version. + Denne version af Signal udløber om %d dage. Opdatér til den nyeste version Indtast kodeord @@ -2106,11 +2106,11 @@ Modtog en nøgle besked, for en ugyldig protokol-version Du vil blive påmindet senere. Bekræftelse af din PIN vil blive krævet om %1$d dage Fortæl Signal hvad du synes - For at gøre Signal til den bedste besked-app på planeten, vil vi meget gerne høre din feedback. + For at gøre Signal til den bedste besked-app på planeten, vil vi meget gerne bede om din feedback Læs mere Afvis - Forskning af Signal - Vi tror på fortrolighed.

Signal sporer ikke dig eller indsamler dine data. For at forbedre Signal for alle er vi afhængige af brugerfeedback, og vi vil meget gerne høre din.

Vi kører en undersøgelse for at forstå, hvordan du bruger Signal. Vores undersøgelse indsamler ikke nogen data, der identificerer dig. Hvis du er interesseret i at dele yderligere feedback, har du mulighed for at give kontaktoplysninger.

Hvis du har et par minutter og feedback at tilbyde, vil vi meget gerne høre fra dig.

]]>
+ Signal forskning + Vi tror på fortrolighed.

Signal sporer dig ikke eller indsamler dine data. For at forbedre Signal for alle er vi afhængige af feedback fra brugerne, og vi vil meget gerne høre din.

Vi laver en undersøgelse for at forstå, hvordan du bruger Signal. Vores undersøgelse indsamler ingen data der identificerer dig. Hvis du er interesseret i at dele yderligere feedback, har du mulighed for at afgive dine kontaktoplysninger.

Hvis du har et par minutter og noget feedback at tilbyde, vil vi meget gerne høre fra dig.

]]>
Tag undersøgelsen Nej tak Undersøgelsen er startet af Surveygizmo på det sikre domæne surveys.signalusers.org @@ -2254,7 +2254,7 @@ Der er %d dage tilbage Ukrypteret taleopkald Videoopkald Fjern %1$s som gruppeadministrator? - \"%1$s\" vil være i stand til at redigere gruppen og dens medlemmer. + \"%1$s\" vil være i stand til at redigere gruppen og dens medlemmer Fjern %1$s fra gruppen? Fjern Kopieret til udklipsholder @@ -2266,8 +2266,8 @@ Der er %d dage tilbage Hvad er forældede grupper? Forældede grupper, er grupper der ikke er kompatible med nye gruppefunktioner, som administratorer og mere beskrivende opdateringer af grupper Kan jeg opgradere en forældet gruppe? - Forældede grupper kan endnu ikke opgraderes til nye grupper, men du kan oprette en ny gruppe med de samme medlemmer, hvis de har i den nyeste version af Signal. - Signal vil tilbyde en måde at opgradere Forældede grupper i fremtiden. + Forældede grupper kan endnu ikke opgraderes til nye grupper, men du kan oprette en ny gruppe med de samme medlemmer, hvis de har i den nyeste version af Signal + Signal vil tilbyde en måde at opgradere Forældede grupper på, i fremtiden Del via Signal Kopiér diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index ba50af0b8..75b5fd6a8 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -128,11 +128,11 @@ Gruppenfoto entfernen? Signal aktualisieren - Diese Version von Signal wird nicht mehr unterstützt. Um weiterhin Nachrichten senden und empfangen zu können, aktualisiere auf neueste Version. + Diese Signal-Version wird nicht mehr unterstützt. Um weiterhin Nachrichten senden und empfangen zu können, aktualisiere auf neueste Version. Aktualisieren Nicht aktualisieren Warnung - Deine Version von Signal ist veraltet. Du kannst deinen Nachrichtenverlauf einsehen, aber du kannst keine neuen Nachrichten senden oder empfangen bis du aktualisiert hast. + Deine Signal-Version ist veraltet. Bis du Signal aktualisiert hast, kannst du deinen Nachrichtenverlauf einsehen, jedoch keine neuen Nachrichten senden oder empfangen. Kein Webbrowser gefunden. Keine E-Mail-App gefunden. @@ -286,7 +286,7 @@ Nachrichten werden gelöscht … Für mich löschen Für jeden löschen - Diese Nachricht wird für jeden Teilnehmer der Unterhaltung unwiderruflich gelöscht. Gruppenmitglieder werden sehen können, dass du eine Nachricht gelöscht hast. + Originalnachricht nicht gefunden Originalnachricht nicht mehr verfügbar Nachricht konnte nicht geöffnet werden @@ -374,7 +374,7 @@ Für Betrieb ohne Google-Play-Dienste optimieren Dieses Gerät unterstützt keine Google-Play-Dienste. Antippen, um Akkuoptimierungen zu deaktivieren, die Signal daran hindern, Nachrichten im Hintergrund zu empfangen. - Diese Version von Signal ist veraltet. Aktualisiere jetzt um Nachrichten senden und empfangen zu können. + Diese Signal-Version ist veraltet. Aktualisiere jetzt, um Nachrichten senden und empfangen zu können. Jetzt aktualisieren Teilen mit @@ -535,7 +535,7 @@ Name und Bild bearbeiten Gruppe alten Typs Dies ist eine Gruppe alten Typs. Funktionen wie Gruppen-Admins sind nur in Gruppen neuen Typs verfügbar. - Dies ist eine unsichere MMS-Gruppe. Lade deine Kontakte zu Signal ein, um sich vertraulich zu unterhalten und Zugang zu Funktionen, wie Gruppen-Namen, zu haben. + Dies ist eine unverschlüsselte MMS-Gruppe. Lade deine Kontakte zu Signal ein, um dich vertraulich zu unterhalten und Funktionen wie Gruppennamen verwenden zu können. Jetzt einladen Mich über Erwähnungen benachrichtigen @@ -629,7 +629,7 @@ Der Gruppenbeitritt über einen Link wird derzeit noch nicht von Signal unterstützt. Diese Funktion wird in einer kommenden Aktualisierung veröffentlicht. Die von dir verwendete Signal-Version unterstützt keine Gruppen-Links. Aktualisiere auf die neueste Version, um dieser Gruppe über einen Link beizutreten. Signal aktualisieren - Ein gekoppeltes Gerät verwendet eine Signal-Version, die keine Gruppen-Links unterstützt. Aktualisiere Signal auf dem gekoppeltem Gerät, um diese Gruppe beizutreten. + Mindestens ein gekoppeltes Gerät verwendet eine Signal-Version, die keine Gruppen-Links unterstützt. Aktualisiere Signal auf deinen gekoppelten Geräten, um dieser Gruppe beizutreten. Gruppen-Link ist ungültig »%1$s« zur Gruppe hinzufügen? @@ -953,10 +953,10 @@ In dieser Unterhaltung gesendete und empfangene Nachrichten werden %s nach dem Lesen verschwinden. Jetzt aktualisieren - Diese Version von Signal wird heute ablaufen. Aktualisiere zur neuesten Version. + Diese Signal-Version wird heute ablaufen. Aktualisiere zur neuesten Version. - Diese Version von Signal wird morgen ablaufen. Aktualisiere zur neuesten Version. - Diese Version von Signal wird in %d Tagen ablaufen. Aktualisiere zur neuesten Version. + Diese Signal-Version wird morgen ablaufen. Aktualisiere auf die neueste Version. + Diese Signal-Version wird in %d Tagen ablaufen. Aktualisiere auf die neueste Version. Passphrase eingeben @@ -2095,11 +2095,11 @@ Schlüsselaustausch-Nachricht für eine ungültige Protokollversion empfangenWir erinnern dich später. Das Bestätigen deiner PIN wird in %1$d Tagen zwingend erforderlich. Sag Signal, was du denkst - Damit Signal die beste Messaging-App des Planeten wird, möchten wir gerne deine Meinung hören. + Damit Signal die beste Messaging-App der Welt wird, möchten wir gerne deine Meinung hören. Mehr erfahren Verwerfen Signal-Forschung - Wir glauben an Datenschutz.

Signal verfolgt dich nicht und sammelt auch nicht deine Daten. Um Signal für alle zu verbessern, stützen wir uns auf Rückmeldungen der Benutzer, und wir wären sehr an deinen interessiert.

Zum besseren Verständnis darüber, wie du Signal nutzt, führen wir eine Umfrage durch. Unsere Umfrage sammelt keinerlei Daten, die Rückschlüsse auf deine Identität erlauben. Falls du weiteres Feedback geben möchtest, kannst du optional deine Kontaktinformationen hinterlassen.

Wenn du einige Minuten erübrigen kannst, würden wir sehr gerne von dir hören.

]]>
+ Wir glauben an Datenschutz.

Signal verfolgt dich nicht und sammelt auch nicht deine Daten. Um Signal für alle zu verbessern, stützen wir uns auf Rückmeldungen der Benutzer, und wir wären sehr an deinen interessiert.

Zum besseren Verständnis darüber, wie du Signal nutzt, führen wir eine Umfrage durch. Unsere Umfrage sammelt keinerlei Daten, die Rückschlüsse auf deine Identität erlauben. Falls du weiteres Feedback geben möchtest, kannst du optional deine Kontaktinformationen hinterlassen.

Wenn du einige Minuten erübrigen kannst, würden wir sehr gerne von dir hören.

]]>
An Umfrage teilnehmen Nein, danke Diese Umfrage wird bei »Surveygizmo« auf der sicheren Domain »surveys.signalusers.org« gehostet. diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 2ac4154bd..790b65eaf 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -286,7 +286,7 @@ Διαγραφή μηνυμάτων… Διαγραφή για εμένα Διαγραφή για όλους - Αυτό το μήνυμα θα διαγραφτεί μόνιμα για όλους στη συνομιλία. Τα μέλη θα μπορούν να δουν ότι διέγραψες ένα μήνυμα. + Το αρχικό μήνυμα δε βρέθηκε Το αρχικό μήνυμα δεν είναι πια διαθέσιμο Αποτυχία ανοίγματος μηνύματος @@ -2106,6 +2106,7 @@ Μάθε περισσότερα Απόρριψη Έρευνα Signal + Πιστεύουμε στην ιδιωτικότητα.

Το Signal δεν σε παρακολουθεί ούτε συλλέγει τα δεδομένα σου. Για να βελτιώσουμε το Signal για όλους, βασιζόμαστε στα σχόλια των χρηστών και θα εκτιμούσαμε πολύ να ακούσουμε τα δικά σου.

Κάνουμε μαι έρευνα που θα μας βοηθήσει να καταλάβουμε πως χρησιμοποιείς το Signal. Η έρευνά μας δεν συλλέγει δεδομένα που θα μπορούσαν να αποκαλύψουν την ταυτότητά σου. Αν ενδιαφέρεσαι να μας βοηθήσεις με περαιτέρω πληροφορίες και σχόλια, μπορείς να μας δώσεις στοιχεία επικοινωνίας.

Αν έχεις μερικά λεπτά διαθέσιμα, και έχεις κάτι να σχολιάσεις, με χαρά θα θέλαμε να το ακούσουμε.

]]>
Πάρε μέρος στην έρευνα Όχι, ευχαριστώ Αυτή η έρευνα φιλοξενείται από το Surveygizmo στον ασφαλή σύνδεσμο surveys.signalusers.org diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index 1d8c3149b..6b7f1496e 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -284,7 +284,7 @@ Forviŝado de mesaĝoj… Forviŝi nur por mi Forviŝi por ĉiuj - Tiu ĉi mesaĝo estos porĉiame forviŝita por ĉiuj en la interparolo. Anoj vidos, ke vi forviŝis mesaĝon. + Origina mesaĝo ne troveblas Origina mesaĝo ne plu disponeblas Malsukceso malfermi la mesaĝon diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 087fb5978..7850eaa62 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -84,9 +84,9 @@ ¿Bloquear y abandonar «%1$s»? ¿Bloquear a %1$s? - No podrás enviar ni recibir más mensajes en este grupo y sus participantes no podrán agregarte de nuevo al grupo. - Participantes del grupo no podrán agregarte de nuevo. - Participantes del grupo podrán agregarte de nuevo. + No podrás enviar ni recibir más mensajes en este grupo y sus participantes no podrán añadirte de nuevo. + L@s participantes del grupo no podrán añadirte de nuevo. + L@s participantes del grupo podrán añadirte de nuevo. Podréis chatear y llamaros mutuamente. Tu nombre y foto de perfil se compartirá con esta persona. Las personas bloqueadas no podrán llamarte o enviarte mensajes. ¿Desbloquear a %1$s? @@ -210,7 +210,7 @@ ¿Desbloquear a esta persona? ¿Desbloquear este grupo? Podrás volver a recibir mensajes y llamadas de esta persona. - Participantes del grupo podrán agregarte de nuevo. + L@s participantes del grupo podrán añadirte de nuevo. Desbloquear El adjunto excede los límites de tamaño para el mensaje. Cámara no disponible @@ -286,7 +286,7 @@ Eliminando mensajes … Eliminar para mi Eliminar para tod@s - Este mensaje se eliminará para tod@s sus participantes. En el chat se mostrará que lo has eliminado. + No se encuentra el mensaje original El mensaje original ya no está disponible Fallo al abrir mensaje @@ -326,7 +326,7 @@ Identidad verificada - Algunas personas no pueden agregarse a grupos antiguos. + Algunas personas no pueden añadirse a grupos antiguos. Perfil Fallo al establecer la foto de perfil @@ -390,13 +390,13 @@ GIFs Cromos - ¿Agregar participante? - ¿Agregar a %1$s a «%2$s»? - Se ha agregado a %1$s a «%2$s». - Agregar al grupo - Agregar a grupos - No se puede agregar a esta persona a grupos del sistema antiguo. - Agregar + ¿Añadir participante? + ¿Añadir a %1$s a «%2$s»? + Se ha añadido a %1$s a «%2$s». + Añadir al grupo + Añadir a grupos + No se puede añadir a esta persona a grupos del sistema antiguo. + Añadir Selecciona nuev@ admin Hecho @@ -417,9 +417,9 @@ Invitación enviada %d invitaciones enviadas - No se puede agregar automáticamente a %1$s al grupo. Ha recibido una invitación y no podrá ver ningún mensaje hasta que la acepte. + No se puede añadir automáticamente a %1$s al grupo. Ha recibido una invitación y no podrá ver ningún mensaje hasta que la acepte. Saber más - No se puede agregar automáticamente a estas personas al grupo. Han recibido una invitación y no podrán ver ningún mensaje hasta que la acepten. + No se puede añadir automáticamente a estas personas al grupo. Han recibido una invitación y no podrán ver ningún mensaje hasta que la acepten. ¿Abandonar grupo? No podrás enviar o recibir más mensajes en este grupo. @@ -461,18 +461,18 @@ Solicitudes pendientes - No hay solicitudes que mostrar. + No hay solicitudes que ver. Las personas en esta lista han solicitado unirse al grupo vía enlace. - %1$s agregad@ + %1$s añadid@ %1$s denegad@ Hecho - No se puede agregar a esta persona a grupos del sistema antiguo. + No se puede añadir a esta persona a grupos del sistema antiguo. ¿Agregar %1$s a «%2$s»? - ¿Agregar %3$d participantes a «%2$s»? + ¿Añadir %3$d participantes a «%2$s»? - Agregar + Añadir Nombra este grupo Crear grupo @@ -492,18 +492,18 @@ %d participantes no soportan el nuevo sistema de grupos. Este será un grupo del sistema antiguo. - Se creará un grupo del sistema antiguo porque %1$s usa una versión antigua de Signal. Puedes crear un grupo en el nuevo sistema después que esa persona actualice Signal. También puedes no agregar a esa persona al grupo. + Se creará un grupo del sistema antiguo porque %1$s usa una versión antigua de Signal. Podrás crear un grupo en el nuevo sistema si esa persona actualiza Signal. También puedes crear un grupo en el nuevo sistema sin ella, y añadirla cuando haya actualizado. - Se creará un grupo del sistema antiguo porque %1$d participante usa una versión antigua de Signal. Puedes crear un grupo en el nuevo sistema después que esa persona actualice Signal. También puedes no agregar a esa persona al grupo. - Se creará un grupo del sistema antiguo porque %1$d participantes usan una versión antigua de Signal. Puedes crear un grupo en el nuevo sistema después que esas personas actualicen Signal. También puedes no agregar a esas personas al grupo. + Se creará un grupo del sistema antiguo porque %1$d persona usa una versión antigua de Signal. Podrás crear un grupo en el nuevo sistema si esa persona actualiza Signal. También puedes crear un grupo en el nuevo sistema sin ella, y añadirla cuando haya actualizado. + Se creará un grupo del sistema antiguo porque %1$d personas usan una versión antigua de Signal. Podrás crear un grupo en el nuevo sistema si esas personas actualizan Signal. También puedes crear un grupo en el nuevo sistema sin ellas, y añadirlas cuando hayan actualizado. Desaparición de mensajes Invitaciones pendientes Solicitudes e invitaciones - Agregar participantes + Añadir participantes Editar detalles del grupo - ¿Quién puede agregar participantes? + ¿Quién puede añadir participantes? ¿Quién puede modificar los detalles del grupo? Enlace del grupo Bloquear grupo @@ -515,19 +515,19 @@ Hasta %1$s Inactivo Activo - Mostrar participantes - Mostrar todos + Ver participantes + Ver tod@s Ninguna %d invitad* %d invitad@s - %d participante agregad*. - %d participantes agregad@s. + %d participante añadid@. + %d participantes añadid@s. No dispones de permisos para hacerlo - Alguien que has agregado no soporta el nuevo sistema de grupos ya que debe actualizar Signal. + Alguien que has añadido no soporta el nuevo sistema de grupos ya que debe actualizar Signal. Fallo al actualizar el grupo No eres participante del grupo Fallo al actualizar el grupo, por favor inténtalo más tarde @@ -557,9 +557,9 @@ Hasta %1$s Inactivo Activo - Agregar a un grupo - Mostrar todos los grupos - Mostrar todos + Añadir a un grupo + Ver todos los grupos + Ver tod@s No hay grupos en común %d grupo en común @@ -632,9 +632,9 @@ Alguno(s) de tus dispositivos enlazados no soporta enlaces para compartir grupos. Actualiza Signal en esos dispositivos para unirte a este grupo vía enlace. Enlace a grupo inválido - ¿Agregar a %1$s al grupo? + ¿Añadir a %1$s al grupo? ¿Denegar la solicitud de %1$s? - Agregar + Añadir Denegar Avatar del grupo @@ -789,9 +789,9 @@ Has creado el grupo. Grupo actualizado. - Has agregado a «%1$s». - %1$s ha agregado a «%2$s». - %1$s te ha agregado al grupo. + Has añadido a %1$s. + %1$s ha añadido a %2$s. + %1$s te ha añadido al grupo. Te has incorporado al grupo. %1$s se han unido al grupo. @@ -845,8 +845,8 @@ Has aceptado la invitación para participar en el grupo. %1$s ha aceptado la invitación para el grupo. - Has agregado a «%1$s». - %1$s ha agregado a «%2$s». + Has añadido a %1$s. + %1$s ha añadido a %2$s. Has modificado el nombre del grupo a «%1$s». %1$s ha modificado el nombre del grupo a «%2$s». @@ -860,9 +860,9 @@ %1$s ha actualizado quién puede editar los detalles del grupo a «%2$s». Se ha cambiado quién puede editar los detalles del grupo a «%1$s». - Has actualizado quién puede editar la lista de participantes del grupo a «%1$s». - %1$s ha actualizado quién puede editar la lista de participantes del grupo a «%2$s». - Ahora, «%1$s» pueden editar la lista de participantes del grupo. + Has actualizado quién puede añadir participantes al grupo a «%1$s». + %1$s ha actualizado quién puede añadir participantes al grupo a «%2$s». + Ahora, «%1$s» pueden añadir participantes al grupo. Has activado el enlace para compartir el grupo sin necesitar confirmación de admin. Has activado el enlace para compartir el grupo tras confirmación de admin. @@ -1043,12 +1043,12 @@ Vídeollamada de Signal … Iniciar llamada Llamada en grupo - Mostrar participantes + Ver participantes Tu vídeo está desactivado En esta llamada · %1$d persona - En esta llamada · %1$d persons + En esta llamada · %1$d personas Selecciona tu país @@ -1138,7 +1138,7 @@ de intercambio de claves! Se recibió un mensaje de intercambio de claves para una versión no válida del protocolo. - Has recibido un mensaje con unas cifras de seguridad nuevas. Toca para procesarlo y mostrarlo. + Has recibido un mensaje con unas cifras de seguridad nuevas. Toca para procesarlo y verlo. Has restablecido la sesión segura. %s restableció la sesión segura. Duplicar mensaje. @@ -1204,7 +1204,7 @@ Se recibió un mensaje de intercambio de claves para una versión no válida del Identidad no verificada Ha sido mposible procesar el mensaje Solicitud de chat - %1$s te ha agregado al grupo + %1$s te ha añadido al grupo %1$s te ha invitado al grupo Foto GIF @@ -1425,12 +1425,12 @@ Se recibió un mensaje de intercambio de claves para una versión no válida del %1$s no usa Signal. Asegúrate de introducir el alias correcto. De acuerdo Este grupo está completo - No necesitas agregarte a ti mism@ al grupo + No necesitas añadirte a ti mism@ al grupo No hay personas bloqueadas Signal necesita acceso a tus contactos para poder mostrarlos. - Mostrar personas + Ver personas %1$d participante @@ -1617,7 +1617,7 @@ Se recibió un mensaje de intercambio de claves para una versión no válida del Por favor, introduce tu número móvil para recibir el código de verificación por SMS. Puede que tu proveedor aplique un cargo por recibir SMS. Introducir nombre o número - Agregar participantes + Añadir participantes Esta persona no está en tu lista de contactos. BLOQUEAR @@ -1858,8 +1858,8 @@ Se recibió un mensaje de intercambio de claves para una versión no válida del Mostrar Llamadas Tono de llamada - Mostrar invitación - Muestra sugerencia de invitación para personas que todavía no usan Signal + Mostrar invitaciones + Muestra sugerencias para invitar a personas que todavía no usan Signal Tamaño de fuente del mensaje Alguien comienza a usar Signal Prioridad @@ -1880,7 +1880,7 @@ Se recibió un mensaje de intercambio de claves para una versión no válida del Nuevo mensaje para … - Agregar al grupo + Añadir al grupo Llamar @@ -2110,11 +2110,11 @@ Se recibió un mensaje de intercambio de claves para una versión no válida del Para hacer de Signal la mejor aplicación de chat del mundo, nos gustaría saber tu opinión (encuesta en inglés). Saber más Ignorar - Encuestas de uso de Signal - We believe in privacy.

Signal doesn\'t track you or collect your data. To improve Signal for everyone, we rely on user feedback, and we\'d love yours.

We\'re running a survey to understand how you use Signal. Our survey doesn\'t collect any data that will identify you. If you’re interested in sharing additional feedback, you\'ll have the option to provide contact information.

If you have a few minutes and feedback to offer, we\'d love to hear from you.

]]>
- Take the survey + Encuesta de uso de Signal + Creemos en la privacidad.

Signal no analiza el uso que le das, ni recopila tus datos. Para mejorar Signal para tod@s, necesitamos la opinión de l@s usuari@s y especialmente la tuya.

Hemos preparado una encuesta en inglés para entender mejor cómo usas Signal. Nuestra encuenta no recopila ningún detalle que te pueda identificar. Si te interesa incluir información adicional (en inglés), tienes la opción de dejar tus datos de contacto.

Si tienes unos pocos minutos para ofrecernos tu opinión (en inglés), nos encantaría escucharla.

]]>
+ Hacer la encuesta No gracias - The survey is hosted by Surveygizmo at the secure domain surveys.signalusers.org + La encuenta en inglés está alojada en Surveygizmo en el dominio seguro surveys.signalusers.org Icono de transporte Cargando … @@ -2173,7 +2173,7 @@ Se recibió un mensaje de intercambio de claves para una versión no válida del Nunca Desconocido Ver mi número de teléfono - Encontrarme por mi número de teléfono + Encontrarme por número de teléfono Cualquiera Mis contactos Nadie @@ -2243,8 +2243,8 @@ Se recibió un mensaje de intercambio de claves para una versión no válida del Bloquear Desbloquear Añadir a contactos - Agregar a un grupo - Agregar a otro grupo + Añadir a un grupo + Añadir a otro grupo Ver cifras de seguridad Promover a admin Retirar permisos de admin @@ -2254,7 +2254,7 @@ Se recibió un mensaje de intercambio de claves para una versión no válida del Llamada no segura Vídeollamada ¿Retirar los permisos de admin a %1$s? - %1$s podrá modificar este grupo, incluida la lista de participantes. + %1$s podrá modificar este grupo y añadir participantes. ¿Expulsar a %1$s del grupo? Eliminar Copiado al portapapeles @@ -2274,6 +2274,6 @@ Se recibió un mensaje de intercambio de claves para una versión no válida del Código QR Compartir Copiado al portapapeles - Actualmente, el enlace no está activo + Enlace (actualmente) inactivo diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 51a633aeb..6e63377d0 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -280,7 +280,7 @@ Sõnumite kustutamine… Kustuta minu jaoks Kustuta kõigi jaoks - See sõnum kustutatakse jäädavalt kõigi vestluses osalejate jaoks. Liikmed näevad, et sa kustutasid sõnumi. + Originaalsõnumit ei leitud Originaalsõnum pole enam saadaval Sõnumi avamine ebaõnnestus diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index fde6c7065..96fb9ecd0 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -128,8 +128,11 @@ Taldeko argazkia ezabatu? Eguneratu Signal + Aplikazioaren bertsio honek jadanikez dauka bermerik. Mezuak jasotzen eta bidaltzen jarraitzeko, eguneratu azken bertsiora. Eguneratu + Ez Eguneratu Markatutako zenbakia ez dago Signalen erregistratuta. SMS bidez gonbidatu nahiko zenuke? + Zure Signal bertsioa iraungi da. Zure mezuen historia ikusi dezakezu, baina ezingo duzu mezuak bidali edo jaso eguneratu arte. Ezin izan da nabigatzailerik aurkitu. Ez da e-posta aplikaziorik aurkitu. @@ -283,7 +286,7 @@ Mezuak ezabatzen… Ezabatu niretzat Ezabatu guztientzat - Mezu hau betiko ezabatuko da solasaldian dago edonorentzat. Kideek ikusi ahal izango dute mezu bat ezabatu duzula. + Jatorrizko mezua ez da aurkitu Jatorrizko mezua ez dago eskuragarri jada Akatsa mezua irekitzean @@ -371,6 +374,8 @@ Optimizatu Google Play-ren zerbitzuetarko Gailu honek ez dauka Play Services erabiltzerik. Signal irekita ez dagoenean, sistemaren bateria optimizazioek galarazten diote mezuak jasotzea. Ukitu sistemaren bateria optimizazioak ezgaitzeko. + Signalen bertsio hau iraungi da. Eguneratu orain mezuak bidali eta jaso ahal izateko. + Eguneratu orain Honekin partekatu: Eranskin anitzak bakarrik irudi eta bideoetan onartzen dira @@ -498,6 +503,8 @@ Kideen eskaera & gonbidapenak Kideak gehitu Editatu taldeari buruzko informazioa + Nork gehitu ditzake kide berriak? + Nork edita dezake talde honen informazioa? Talderako esteka Blokeatu taldea Desblokeatu taldea @@ -527,6 +534,9 @@ Ezin izan da taldea eguneratu sare errore bat dela eta; mesedez, beranduago saia zaitez Editatu izena eta irudia Jarauntsitako Taldea + Hau Jarauntsitako Talde bat da. Talde administratzaile moduko ezaugarriak Talde Berrietan erabil daitezke bakarrik. + Hau segurtasunik gabeko MMS Talde bat da. Modu pribatuan txateatzeko eta talde izenak moduko ezaugarriak erabili ahal izateko, Signalera gonbidatu zure kontaktuak. + Gonbidatu orain Nahi dut aipamenak jakinaraztea Nahi duzu jakinarazpenak jaso mutututako txatetan aipatzen zaituztenean? @@ -619,6 +629,7 @@ Talde batean sartzea link baten bidez oraindik ez dago onartuta Signal-en. Ezaugarri hau eskuragarri egongo da aurrerantzean egingo den egunerapen batean. Erabiltzen ari zare Signalen bertsioak ez du talde estekak onartzen. Eguneratu azken bertsiora esteka bidez talde honetan sartzeko. Eguneratu Signal + Gutxienez lotuta dituzun gailuetako batek Talde estekak onartzen ez dituen Signalen bertsio bat dauka instalatuta. Signal eguneratu lotutako gailuetan talde honetan sartu ahal izateko. Talde esteka hau ez da baliagarria Nahi duzu \"%1$s\" erabiltzailea taldean sartu? @@ -943,6 +954,12 @@ Zure mezuak ez dira iraungiko. Solasaldi honetako mezu guztiak, bai jasoak zein bidaliak, irakurri eta %s igaro ondoren desagertu egingo dira. + Eguneratu orain + Singalen bertsio hau gaur iraungiko da. Eguneratu azken bertsiora. + + Signalen bertsio hau bihar iraungiko da. Eguneratu azken bertsiora. + Signalen bertsio hau %d egunetan iraungiko da. Eguneratu azken bertsiora. + Pasahitza sartu Signal ikonoa @@ -1020,10 +1037,19 @@ %1$s erabiltzaileari deitzeko, Signalek zure kamera erabiltzeko baimena behar du Signal %1$s Deitzen… + Talde Deia Signal ahots deia… Signal bideo deia… + Hasi Deia + Talde Deia + Kideak ikusi + Zure bideoa itzalita dago + + Dei honetan - pertsona %1$d + Dei honetan - %1$d pertsona + Aukeratu zure herrialdea Zure herrialdearen @@ -2080,9 +2106,15 @@ Inportatu \'SMSBackup and Restorekin\' bateragarria den enkriptatu gabeko babesk Beranduago gogoraraziko dizugu. PIN bat sortzea beharrezkoa izango da %1$d eguneta. Beranduago gogoraraziko dizugu. Zure PINa baieztatzea derrigorrezkoa izango da %1$d egunetan. + Esan Signali pentsatzen duzuna + Signal munduko mezularitza aplikaziorik onena bilakatzeko, zure iritzia entzutea gustatuko litzaiguke. Gehiago jakin Baztertu + Signal Ikerketa + Pribatutasunean sinesten dugu

Signalek ez du jarraitzen egiten duzuna edo zure datuak bildu. Signal guztiontzat hobetzeko, erabiltzaileen iruzkinetan oinarritzen gara, eta zurea edukitzea gustatuko litzaiguke.

Inkesta bat egiten ari gara Signal nola erabiltzen duzun ulertzeko. Gure inkestak identifika zaitzakeen daturik ez du biltzen. Informazio gehiago partekatu nahi baduzu, kontakturako informazioa emateko aukera izango duzu.

Minutu batzuk eta iruzkinak egiteko baduzu, gustatuko litzaiguke zure iritzia entzutea.

]]>
+ Inkesta egin Ez, eskerrik asko + Inkesta Surveygizmo zerbitzuan dago ostatuta, surveys.signalusers.org domeinuan Garraio ikonoa Kargatzen… @@ -2222,6 +2254,8 @@ Inportatu \'SMSBackup and Restorekin\' bateragarria den enkriptatu gabeko babesk Ahots dei ez segurua Bideo deia %1$s erabiltzailea talde administratzaile gisa ezabatu nahi duzu? + \"%1$s\" erabiltzailea talde hau eta bere kideak editatzeko gai izango da. + Ezabatu nahi duzu %1$s kidea taldetik? Ezabatu Arbelera kopiatuta Administratzailea @@ -2231,6 +2265,9 @@ Inportatu \'SMSBackup and Restorekin\' bateragarria den enkriptatu gabeko babesk Jarauntsitako Taldeak vs. Talde Berriak Zer dira Jarauntsitako Taldeak? Jarauntsitako Taldeak ez dira taldeen ezaugarri berriekin bateragarriak; esate baterako, administratzaileak eta taldeen egunerapenei buruzko informazio zehatzagoa izateko aukerak. + Eguneratu dezaket Jarauntsitako Taldea? + Jarauntsitako Taldeak oraindik ezin dira Talde Berrietara eguneratu, baina Signalen azkenengo bertsioa baduzu, kide berak dituen Talde Berri bat sortu dezakezu. + Signalek Jarauntsitako Taldeak eguneratzeko modu bat eskainiko du aurrerantzean. Partekatu Signal bidez Kopiatu diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index c32b3e789..fadb851c6 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -286,7 +286,7 @@ حذف پیام‌‌ها… حذف برای من حذف برای همه - این پیام به طور دائم برای همه در این مکالمه حذف خواهد شد. اعضا قادر خواهند بود تا ببینند که شما پیامی را حذف کرده‌اید. + پیام اصلی یافت نشد پیام اصلی دیگر در دسترس نیست عدم موفقیت در باز کردن پیام diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 1c240b9f0..6e046b5a3 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -129,6 +129,7 @@ Päivitä Signal Päivitä + Älä päivitä Varoitus Verkkoselainta ei löytynyt. @@ -283,7 +284,7 @@ Viestejä poistetaan… Poista minulta Poista kaikilta - Tämä viesti poistetaan pysyvästi kaikilta keskustelun jäseniltä. Jäsenet voivat nähdä, että olet poistanut viestin. + Alkuperäistä viestiä ei löytynyt Alkuperäinen viesti ei ole enää saatavilla Viestin avaaminen epäonnistui @@ -371,6 +372,7 @@ Optimoi Play Servicesin puuttumista Tämä laite ei tue Play Servicesiä. Napauta tästä ottaaksesi pois päältä virransäästöominaisuudet, jotka estävät Signalia hakemasta viestejä taustalla. + Päivitä nyt Jaa Useiden tiedostojen liittämistä tuetaan vain kuvilla ja videoilla @@ -498,6 +500,8 @@ Jäsenpyynnöt & kutsut Lisää jäseniä Muokkaa ryhmän tietoja + Ketkä voivat lisätä uusia jäseniä? + Ketkä voivat muokata tämän ryhmän tietoja? Ryhmälinkki Estä ryhmä Poista ryhmän esto @@ -942,6 +946,7 @@ Viestisi eivät vanhene. Lähetetyt ja vastaanotetut viestit katoavat tästä keskustelusta %s siitä, kun ne on nähty. + Päivitä nyt Syötä salalause Signal-kuvake @@ -1019,9 +1024,12 @@ Jotta voit soittaa yhteystiedolle %1$s, Signal tarvitsee lupaa käyttää kameraa. Signal %1$s Soitetaan… + Ryhmäpuhelu Signal-puhelu… Signal-videopuhelu… + Aloita puhelu + Ryhmäpuhelu Valitse maasi @@ -2215,6 +2223,7 @@ Vastaanotetiin avaintenvaihtoviesti, joka kuuluu väärälle protokollaversiolle Salaamaton äänipuhelu Videopuhelu Poista %1$s ryhmän ylläpitäjistä? + Poista %1$s ryhmästä? Poista Kopioitu leikepöydälle Ylläpitäjä diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index dd0e6bb61..b9da09f6e 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -128,7 +128,7 @@ Supprimer la photo du groupe ? Mettre Signal à jour - Mettre à jour + Mise à jour Avertissement Aucun navigateur Web n’a été trouvé. @@ -226,7 +226,7 @@ Signal a besoin de l’autorisation Microphone afin d’enregistrer des vidéos, mais elle a été refusée. Veuillez accéder aux paramètres de l’appli, sélectionner Autorisations et activer Microphone et Appareil photo. Signal a besoin de l’autorisation Microphone afin d’enregistrer des vidéos. %1$s %2$s - Signal ne peut ni envoyer de textos ni de messages multimédias, car ce n’est pas votre appli de textos par défaut. Souhaitez-vous changer cela dans vos paramètres d’Android ? + Signal ne peut ni envoyer de textos ni de messages multimédias, car ce n’est pas votre appli de textos par défaut. Voulez-vous changer cela dans vos paramètres d’Android ? Oui Non %1$d sur %2$d @@ -283,7 +283,7 @@ Suppression des messages… Supprimer pour moi Supprimer pour tous - Ce message sera irrémédiablement supprimé pour tous les membres de la conversation. Les membres pourront voir que vous avez supprimé un message. + Le message original est introuvable Le message original n’est plus disponible Échec d’ouverture du message @@ -398,7 +398,7 @@ Vous avez quitté « %1$s ». Partager vos nom et photo de profil avec ce groupe ? - Souhaitez-vous que vos nom et photo de profil soient visibles pour tous les membres actuels et futurs de ce groupe ? + Voulez-vous que vos nom et photo de profil soient visibles pour tous les membres actuels et futurs de ce groupe ? Rendre visible Vous @@ -588,16 +588,16 @@ Valeur par défaut Réinitialisation du lien de groupe Exiger qu’un administrateur approuve les nouveaux membres qui se joignent grâce au lien de groupe. - Souhaitez-vous vraiment réinitialiser le lien du groupe ? Personne ne pourra plus se joindre au groupe grâce au lien actuel. + Voulez-vous vraiment réinitialiser le lien du groupe ? Personne ne pourra plus se joindre au groupe grâce au lien actuel. Code QR Les personnes qui balaieront ce code pourront se joindre à votre groupe. Les administrateurs devront quand même approuver les nouveaux membres si vous avez activé ce paramètre. Partager le code - Souhaitez-vous révoquer l’invitation envoyée à %1$s ? + Voulez-vous révoquer l’invitation envoyée à %1$s ? - Souhaitez-vous révoquer l’invitation envoyée par %1$s ? - Souhaitez-vous révoquer %2$d invitations envoyées par %1$s ? + Voulez-vous révoquer l’invitation envoyée par %1$s ? + Voulez-vous révoquer %2$d invitations envoyées par %1$s ? Vous êtes déjà membre @@ -607,7 +607,7 @@ Une erreur réseau est survenue. Ce lien de groupe est inactif Impossible de récupérer les renseignements du groupe, veuillez réessayer plus tard - Souhaitez-vous vous joindre à ce groupe et partager votre nom et votre photo avec ses membres ? + Voulez-vous vous joindre à ce groupe et partager votre nom et votre photo avec ses membres ? Un administrateur de ce groupe doit approuver votre demande avant que vous puissiez vous joindre à ce groupe. Quand vous demandez à vous joindre, votre nom et votre photo sont partagés avec ses membres. Groupe · %1$d membre @@ -1529,7 +1529,7 @@ Veuillez examiner ce journal provenant de mon appli : %1$s Échec réseau. Veuillez réessayer. - Souhaitez-vous importer vos textos existants dans la base de données chiffrée de Signal ? + Voulez-vous importer vos textos existants dans la base de données chiffrée de Signal ? La base de données par défaut du système ne sera aucunement modifiée ni altérée. Ignorer Importer @@ -1798,7 +1798,7 @@ L’historique et les contenus multimédias des messages de plus de %1$s seront irrémédiablement supprimés de votre appareil. Toutes les conversations seront irrémédiablement réduites aux %1$s messages les plus récents. Tout l’historique et tous les contenus multimédias des messages seront irrémédiablement supprimés de votre appareil. - Souhaitez-vous vraiment supprimer tout l’historique des messages ? + Voulez-vous vraiment supprimer tout l’historique des messages ? Tout l’historique des messages sera irrémédiablement supprimé. Cette action ne peut pas être annulée. Tout supprimer maintenant Pour toujours @@ -2224,7 +2224,7 @@ Les groupes hérités comparés aux nouveaux Que sont les groupes hérités ? Les groupes hérités sont des groupes qui ne sont pas compatibles avec les fonctions des nouveaux groupes, telles que les administrateurs et les mises à jour plus descriptives des groupes. - Puis-je mettre un groupe hérité à niveau ? + Puis-je transformer un groupe hérité en nouveau groupe ? Partager avec Signal Copier diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index a6070ff9b..ed296a23a 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -90,6 +90,7 @@ Više nećete primati poruke ili ažuriranja iz ove grupe, a članovi grupe vas neće moći naknadno dodati u grupu. Članovi grupe vas neće moći ponovno dodati u ovu grupu. Članovi grupe će vas moći ponovno dodati u ovu grupu. + Moći će te se razmjenjivati poruke i pozive, a vaše ime i fotografija podijelit će se s njima. Blokirani korisnici vas neće moći nazvati niti vam slati poruke. Odblokiraj %1$s? Odblokiraj @@ -130,8 +131,11 @@ Ukloniti sliku grupe? Ažuriraj Signal + Ova verzija aplikacije više nije podržana. Da biste nastavili slati i primati poruke, ažurirajte na najnoviju verziju. Ažuriraj + Ne ažuriraj Upozorenje + Vaša verzija Signala je istekla. Možete pregledati svoju povijest poruka, ali nećete moći slati ili primati poruke dok ne ažurirate. Nije pronađen nijedan web preglednik. Nije pronađena nijedna aplikacija za e-poštu. @@ -214,7 +218,9 @@ Privitak premašuje ograničenja veličine za vrstu poruke koju šaljete. Kamera nije dostupna Nije moguće snimiti zvuk! + Ne možete slati poruke jer više niste član/ica ove grupe. Na vašem uređaju nema dostupne aplikacije za obradu ove poveznice. + Vaš zahtjev za pridruživanje poslan je administratoru grupe. Biti će te obaviješteni kada nešto poduzmu. Poništi zahtjev Za slanje zvučnih poruka, omogućite Signalu pristup vašem mikrofonu. Signal zahtijeva dopuštenje za mikrofon za slanje zvučnih poruka, ali je trajno odbijen. Otvorite postavke aplikacije, odaberite \"Dozvole\" i omogućite \"Mikrofon\". @@ -242,6 +248,7 @@ Izbriši i napusti Da biste uputili poziv za %1$s, omogućite Signalu pristup vašem mikrofonu Da biste uputili poziv za %1$s, omogućite Signalu pristup vašem mikrofonu i kameri. + Više opcija je sada u \"Postavke grupe\" %d nepročitana poruka @@ -289,7 +296,7 @@ Brisanje poruka… Izbriši za mene Izbriši za sve - Ova poruka će biti trajno izbrisana za sve osobe u razgovoru. Članovi će moći vidjeti da ste izbrisali poruku. + Originalna poruka nije pronađena Originalna poruka više nije dostupna Otvaranje poruke nije uspjelo @@ -297,6 +304,7 @@ Za brzi odgovor, prijeđite prstom ulijevo po bilo kojoj poruci Odlazni medijski zapisi koji su vidljivi samo jednom se automatski uklanjaju nakon što su poslani Već ste pregledali ovu poruku + U ovom razgovoru možete dodati osobne bilješke.\nAko vaš račun ima povezane uređaje, nove će se bilješke sinkronizirati. Na vašem uređaju nije instaliran preglednik. @@ -350,6 +358,7 @@ Uslikaj Odaberi iz galerije Ukloni sliku + Za snimanje fotografija potrebno je dopuštenje kamere. Sada %dm @@ -379,6 +388,8 @@ Optimiziraj za nedostatak Play Services Ovaj uređaj ne podržava Play Services. Pritisnite kako biste onemogućili sistemsku optimizaciju baterije koja spriječava Signal da dohvaća poruke dok je uređaj neaktivan. + Ova verzija Signala je istekla. Ažurirajte odmah da biste slali i primali poruke. + Ažuriraj sada Podijeli s Višestruki privitci podržani su samo za slike i videozapise @@ -393,6 +404,9 @@ GIF-ovi Naljepnice + Dodaj člana? + Dodaj \"%1$s\" u \"%2$s\"? + \"%1$s\" je dodan/a u \"%2$s\". Dodaj u grupu Dodaj u grupe Ovu osobu nije moguće dodati u naslijeđene grupe. @@ -418,13 +432,16 @@ Poslane su %d pozivnice Poslano je %d pozivnica + Ne možete automatski dodati \"%1$s\" u grupu.\n\nKorisnik je pozvan i neće vidjeti poruke grupe dok ne prihvati pozivnicu. Saznaj više + Ne možete automatski dodati ove korisnike u grupu.\n\nKorisnik je pozvan u grupu i neće vidjeti poruke grupe dok ne prihvati pozivnicu. Napusti grupu? Nećete više biti u mogućnosti slati i primati poruke u ovoj grupi. Napusti Odaberi novog administratora Prije nego što napustite, morate odabrati novog administratora za ovu grupu. + Odaberite administratora Onemogući Pregledajte bilo koju poveznicu @@ -434,6 +451,11 @@ Grupna poveznica nije aktivna %1$s · %2$s + + %1$d član + %1$d člana + %1$d članova + Grupne pozivnice na čekanju Zahtjevi @@ -445,11 +467,30 @@ Pojedinosti o osobama koje su pozvali drugi članovi grupe nisu prikazani. Ako se pozvane osobe odluče pridružiti, njihovi će se podaci tada podijeliti s grupom. Neće vidjeti poruke u grupi dok se ne pridruže. Opozovi pozivnicu Opozovi pozivnice + + Opozovi pozivnicu + Opozovi %1$d pozivnice + Opozovi %1$d pozivnice + + + Pogreška pri opozivu pozivnice + Pogreška pri opozivu pozivnica + Pogreška pri opozivu pozivnica + + Zahtjevi za članove na čekanju + Nema zahtjeva za članove Osobe s ovog popisa pokušavaju se pridružiti ovoj grupi putem grupne poveznice. + Dodan/a je \"%1$s\" + Odbijen/a je “%1$s“ Gotovo Ovu osobu nije moguće dodati u naslijeđene grupe. + + Dodaj \"%1$s\" u \"%2$s\"? + Dodaj %3$d člana u \"%2$s\"? + Dodaj %3$d članova u \"%2$s\"? + Dodaj Dodijelite naziv grupi @@ -464,6 +505,7 @@ Odabrali ste kontakt koji ne podržava Signal grupe, pa će ovo biti MMS grupa. Ukloni SMS kontakt + Ukloniti %1$s iz ove grupe? %d član ne podržava Nove grupe, tako da će ovo biti Naslijeđena grupa. %d člana ne podržavaju Nove grupe, tako da će ovo biti Naslijeđena grupa. @@ -479,9 +521,14 @@ Poruke koje nestaju Grupne pozivnice na čekanju + Zahtjevi za članove i pozivnice Dodaj članove + Uredi detalje o grupi + Tko može dodati nove članove? + Tko može urediti detalje o grupi? Grupna poveznica Blokiraj grupu + Odblokiraj grupu Napusti grupu Utišaj obavijesti Prilagođene obavijesti @@ -490,17 +537,41 @@ Isključeno Uključeno Prikaži sve članove + Prikaži sve Nijedno + + %d pozvan + %d pozvana + %d pozvano + + + %d član dodan. + %d člana dodana. + %d članova dodano. + Nemate prava na ovo Netko koga ste dodali ne podržava nove grupe i mora ažurirati Signal Ažuriranje grupe nije uspjelo Vi niste član ove grupe + Ažuriranje grupe nije uspjelo, pokušajte ponovno + Ažuriranje grupe nije uspjelo zbog mrežne pogreške, pokušajte ponovno Uredi naziv i sliku Naslijeđena grupa + Ovo je Naslijeđena grupa. Značajke poput grupnih administratora su dostupne samo za Nove grupe. + Ovo je nesigurna MMS grupa. Da biste privatno razgovarali i pristupili značajkama poput naziva grupa, pozovite svoje kontakte u Signal. + Pozovi sada + Obavijesti me samo za Spominjanja Primi obavijesti kad vas se spominje u utišanim razgovorima? + Zadano (Obavijesti me) + Zadano (Ne obavještavaj me) + Uvijek me obavijesti + Ne obavještavaj me + Dodaj u kontakte na uređaju + Ovaj korisnik je u vašim kontaktima Poruke koje nestaju + Boja razgovora Blokiraj Odblokiraj Prikaži sigurnosni broj @@ -510,6 +581,14 @@ Isključeno Uključeno Dodaj u grupu + Prikaži sve grupe + Prikaži sve + Nema zajedničkih grupa + + %d zajednička grupa + %d zajedničke grupe + %d zajedničkih grupa + Uredite ime i sliku Poruka Glasovni poziv @@ -525,7 +604,7 @@ Poruke Koristi prilagođene obavijesti Zvuk obavijesti - Vibriraj + Vibracija Postavke poziva Melodija zvona Omogućeno @@ -533,8 +612,12 @@ Zadano Grupna poveznica koja se može dijeliti + Upravljanje i dijeljenje Grupna poveznica Podijeli + Poništi poveznicu + Zahtjevi za članove + Odobri nove članove Omogućeno Onemogućeno Zadano @@ -542,21 +625,42 @@ Zahtjeva da administrator odobri pridruživanje novih članova putem grupne poveznice. Jeste li sigurni da želite poništiti grupnu poveznicu? Osobe se više neće moći pridružiti grupi koristeći trenutnu poveznicu. + QR kôd Osobe koji skeniraju ovaj kôd moći će se pridružiti vašoj grupi. Administratori će i dalje trebati odobriti nove članove ako je ta postavka uključena. + Podijeli kôd + Želite li opozvati pozivnicu koju ste poslali %1$s? + + Želite li opozvati pozivnicu koju je poslao/la %1$s? + Želite li opozvati %2$d pozivnice koje je poslao/la %1$s? + Želite li opozvati %2$d pozivnica koje je poslao/la %1$s? + + Već ste član Pridruži se + Zahtjev za pridruživanje + Nije moguće pridružiti se grupi. Molimo pokušajte ponovo kasnije Došlo je do mrežne pogreške. Grupna poveznica nije aktivna + Nije moguće dohvatiti detalje o grupi. Molimo pokušajte ponovo kasnije Želite li se pridružiti ovoj grupi i podijeliti svoje ime i fotografiju s članovima? + Administrator ove grupe mora odobriti vaš zahtjev prije nego što se možete pridružiti ovoj grupi. Kada zatražite pridruživanje, vaše će ime i fotografija biti podijeljena sa članovima grupe. + + Grupa · %1$d član + Grupa · %1$d člana + Grupa · %1$d članova + Grupne poveznice uskoro dolaze Ažurirajte Signal da biste koristili grupne poveznice Pridruživanje grupi putem poveznice još nije podržano u Signalu. Ova će značajka biti objavljena u nadolazećem ažuriranju. Verzija Signala koju upotrebljavate ne podržava grupne poveznice. Ažurirajte na najnoviju verziju da biste se pridružili ovoj grupi putem poveznice. Ažuriraj Signal + Na jednom ili više povezanih uređaja pokrenuta je verzija Signala koja ne podržava grupne poveznice. Ažurirajte Signal na povezanim uređajima da biste se pridružili ovoj grupi. Grupna poveznica je nevažeća + Dodaj \"%1$s\" u grupu? + Odbij zahtjev od “%1$s”? Dodaj Odbij @@ -615,6 +719,11 @@ Želite li izbrisati odabrane stavke? Želite li izbrisati odabrane stavke? + + Ovo će trajno izbrisati odabranu datoteku. Također će se izbrisati i sve tekstualne poruke povezane s ovom datotekom. + Ovo će trajno izbrisati %1$d odabrane datoteke. Također će se izbrisati i sve tekstualne poruke povezane s ovim datotekama. + Ovo će trajno izbrisati %1$d odabranih datoteka. Također će se izbrisati i sve tekstualne poruke povezane s ovim datotekama. + Brisanje Brisanje poruka… Odaberi sve @@ -622,12 +731,33 @@ Poredaj po Najnovije Najstarije + Upotrebljeni prostor za pohranu + Sva upotreba prostora za pohranu + Prikaz rešetke + Prikaz popisa + Odabrano + + %1$d stavka %2$s + %1$d stavke %2$s + %1$d stavki %2$s + + + %1$d stavka + %1$d stavke + %1$d stavki + Datoteka Zvuk Video Slika + Poslao/la %1$s + Poslali ste vi + Poslao/la %1$s za %2$s + Poslali ste vi za %1$s Predstavljamo reakcije + Dodirnite i držite bilo koju poruku da brzo podijelite kako se osjećate. + Podsjeti me kasnije Potvrdite svoj Signal PIN Povremeno ćemo zatražiti da potvrdite svoj PIN kako biste ga zapamtili. Potvrdite PIN @@ -645,6 +775,7 @@ Pogreška pri preuzimanju MMS poruke, pritisnite za ponovni pokušaj Pošalji za %s + Otvori kameru Pritisni za odabir slike profila @@ -670,6 +801,7 @@ Primljena je poruka koja je šifrirana pomoću stare verzije Signala koja više nije podržana. Zamolite pošiljatelja da ažurira na najnoviju verziju i ponovo pošalje poruku. Napustili ste grupu. Ažurirali ste grupu. + Grupa je ažurirana. Vi ste zvali Kontakt vas je zvao Propušteni poziv @@ -682,26 +814,94 @@ %1$s je onemogućio poruke koje nestaju. Postavili ste da poruke nestaju za %1$s. %1$s je postavio/la da poruke nestaju za %2$s. + Postavljeno je da poruke nestaju za %1$s. + %1$s je promijenio/la svoje ime na profilu u %2$s. + %1$s je promijenio/la svoje ime na profilu iz %2$s u %3$s. + %1$s je promijenio/la svoj profil. Stvorili ste grupu. Grupa je ažurirana. + Dodali ste %1$s. + %1$s je dodao/la %2$s. + %1$s vas je dodao/la u grupu. Pridružili ste se grupi. %1$s se pridružio/la grupi. + Uklonili ste %1$s. + %1$s je uklonio/la %2$s. + %1$s vas je uklonio/la iz grupe. Napustili ste grupu. + %1$s je napustio/la grupu. + Više niste član ove grupe. + %1$s više nije član/ica grupe. + Postavili ste %1$s za administratora. + %1$s je postavio/la %2$s za administratora. + %1$s vas je postavio/la za administratora. + Opozvali ste administratorska prava za %1$s. + %1$s je opozvao vaša administratorska prava. + %1$s je opozvao administratorska prava za %2$s. + %1$s je sada administrator. Sada ste administrator. + %1$s više nije administrator. + Više niste administrator. + Pozvali ste %1$s u grupu. + %1$s vas je pozvao/la u grupu. + + %1$s je pozvao/la 1 osobu u grupu. + %1$s je pozvao/la %2$d osobe u grupu. + %1$s je pozvao/la %2$d osoba u grupu. + Pozvani ste u grupu. + + 1 osoba je pozvana u grupu. + %1$d osobe su pozvane u grupu. + %1$d osoba je pozvano u grupu. + + + Opozvali ste pozivnicu u grupu. + Opozvali ste %1$d pozivnice u grupu. + Opozvali ste %1$d pozivnica u grupu. + + + %1$s je opozvao pozivnicu u grupu. + %1$s je opozvao %2$d pozivnice u grupu. + %1$s je opozvao %2$d pozivnica u grupu. + + Netko je odbio poziv u grupu. + Odbili ste poziv u grupu. + %1$s je opozvao/la vašu pozivnicu u grupu. + Administrator je opozvao vašu pozivnicu u grupu. + + Pozivnica u grupu je opozvana. + %1$d pozivnice u grupu su opozvane. + %1$d pozivnica u grupu je opozvano. + + Prihvatili ste poziv u grupu. + %1$s je prihvatio poziv u grupu. + Dodali ste pozvanog člana/icu %1$s. + %1$s je dodao/la pozvanog člana/icu %2$s. + Promijenili ste naziv grupe u \"%1$s\". + %1$s je promijenio/la naziv grupe u \"%2$s\". + Naziv grupe je promijenjen u \"%1$s\". Promijenili ste sliku grupe. + %1$s je promijenio/la sliku grupe. + Slika grupe je promijenjena. + Promijenili ste tko sve može urediti detalje o grupi na \"%1$s\". + %1$s je promijenio tko sve može uređivati detalje o grupi na \"%2$s\". + Promijenjeno je tko sve može urediti detalje o grupi na \"%1$s\". + Promijenili ste tko može uređivati članstvo grupe na \"%1$s\". + %1$s je promijenio tko može uređivati članstvo grupe na \"%2$s\". + Promijenjeno je tko sve može urediti članstvo grupe na \"%1$s\". Isključili ste grupnu poveznicu s administratorskim odobrenjem. Uključili ste grupnu poveznicu s administratorskim odobrenjem. @@ -723,7 +923,14 @@ Poslali ste zahtjev za pridruživanje grupi. %1$s je zatražio/la pridruživanje grupi putem grupne poveznice. + %1$s je prihvatio vaš zahtjev za pridruživanje ovoj grupi. + %1$s je prihvatio/la zahtjev za pridruživanje u grupu od %2$s. + Prihvatili ste zahtjev za pridruživanje u grupu od %1$s. + Vaš zahtjev za pridruživanje u grupu je odobren. + Odobren je zahtjev za pridruživanje u grupu od %1$s. + Vaš zahtjev za pridruživanje u grupu je odbijen od administratora. + %1$s je odbio zahtjev za pridruživanje u grupu od %2$s. Odbili ste zahtjev za pridruživanje u grupu od %1$s. Otkazali ste zahtjev za pridruživanje ovoj grupi. %1$s je odbio njihov zahtjev za pridruživanje ovoj grupi. @@ -789,6 +996,13 @@ Vaše poruke neće nestati. Poslane i primljene poruke u ovom razgovoru će nestati za %s nakon što ih se vidi. + Ažuriraj sada + Ova verzija Signala istječe danas. Ažurirajte na najnoviju verziju. + + Ova verzija Signala istječe sutra. Ažurirajte na najnoviju verziju. + Ova verzija Signala istječe za %d dana. Ažurirajte na najnoviju verziju. + Ova verzija Signala istječe za %d dana. Ažurirajte na najnoviju verziju. + Unesite lozinku Signal ikona @@ -867,10 +1081,20 @@ Da biste uputili poziv za %1$s, omogućite Signalu pristup vašoj kameri Signal %1$s Uspostava poziva + Grupni poziv Signal glasovni poziv Signal video poziv… + Započni poziv + Grupni poziv + Prikaži sudionike + Vaš video je isključen + + U ovom pozivu · %1$d osoba + U ovom pozivu · %1$d osobe + U ovom pozivu · %1$d osoba + Odaberite svoju državu Unesite pozivni broj @@ -1453,7 +1677,7 @@ Uvezi sigurnosnu kopiju običnog teksta. Kompatibilno s \'SMSBackup And Restore\ DODAJ U KONTAKTE NE DODAJ, ALI MI PROFIL UČINI VIDLJIVIM - Saznajte više.]]> + Saznajte više.]]> Pritisni za skeniranje Učitavanje… Provjereno @@ -1757,7 +1981,7 @@ Uvezi sigurnosnu kopiju običnog teksta. Kompatibilno s \'SMSBackup And Restore\ Novi razgovor Otvori kameru - Stvorite nove uspomene. Započnite slanjem poruke prijatelju. + Stvorite nove uspomene. Započnite slanjem poruke prijateljima. Poništi sigurnu sesiju @@ -1943,6 +2167,7 @@ Uvezi sigurnosnu kopiju običnog teksta. Kompatibilno s \'SMSBackup And Restore\ Podsjetiti ćemo vas kasnije. Potvrđivanje PIN-a postati će obvezno za %1$d dana. Recite što mislite o Signalu + Kako bi Signal bio najbolja aplikacija za razmjenu poruka na planetu, voljeli bismo čuti vaše povratne informacije. Saznaj više Odbaci Signal istraživanja @@ -1973,7 +2198,7 @@ Uvezi sigurnosnu kopiju običnog teksta. Kompatibilno s \'SMSBackup And Restore\ Spremi sigurnosnu kopiju razgovora u vanjskoj pohranu Stvori sigurnosnu kopiju Provjerite lozinku sigurnosne kopije - Testirajte vašu lozinku sigurnosne kopije i provjerite poklapa li se + Testirajte vašu lozinku sigurnosne kopije i provjerite je li točna Unesi lozinku sigurnosne kopije Vrati Ne mogu se uvesti sigurnosne kopije iz novijih verzija Signala @@ -2090,6 +2315,7 @@ Uvezi sigurnosnu kopiju običnog teksta. Kompatibilno s \'SMSBackup And Restore\ Video poziv Ukloni %1$skao administratora grupe? \"%1$s\" će moći uređivati ovu grupu i njene članove + Ukloniti %1$s iz grupe? Ukloni Kopirano u međuspremnik Administrator @@ -2100,6 +2326,8 @@ Uvezi sigurnosnu kopiju običnog teksta. Kompatibilno s \'SMSBackup And Restore\ Što su Naslijeđene grupe? Naslijeđene grupe su grupe koje nisu kompatibilne s značajkama Nove grupe kao što su administratori i opisnija ažuriranja grupa. Mogu li \"nadograditi\" Naslijeđenu grupu? + Naslijeđene grupe se još ne mogu nadograditi u Nove grupe, ali možete stvoriti Novu grupu s istim članovima ako su na najnovijoj verziji Signala. + Signal će u budućnosti ponuditi način za nadogradnju Naslijeđenih grupa. Podijeli putem Signala Kopiraj diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 753ca649a..b82e7060f 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -287,7 +287,7 @@ Üzenetek törlése… Törlés számomra Törlés mindenki számára - Ez az üzenet a beszélgetés minden tagja számára véglegesen törlésre kerül. A tagok számára látható lesz, hogy törölted az üzenetet. + Az eredeti üzenet nem található Az eredeti üzenet már nem érhető el Nem sikerült megnyitni az üzenetet diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 0b7a6dcb4..1baf2c93a 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -286,7 +286,7 @@ Eliminazione messaggi… Elimina per me Elimina per tutti - Questo messaggio verrà eliminato definitivamente per tutti nella conversazione. I membri potranno vedere che hai eliminato un messaggio. + Messaggio originale non trovato Messaggio originale non più disponibile Apertura messaggio fallita diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index b65b96665..a88b06fba 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -134,8 +134,11 @@ להסיר תמונת קבוצה? עדכן את Signal + גרסה זו של היישום אינה נתמכת יותר. כדי להמשיך לשלוח ולקבל הודעות, עדכן אל הגרסה האחרונה. עדכן + אל תעדכן אזהרה + גרסת Signal שלך פגה. אתה יכול לצפות בהיסטורית ההודעות שלך אבל לא תוכל לשלוח או לקבל הודעות עד שתעדכן. דפדפן רשת לא נמצא. יישום דוא״ל לא נמצא. @@ -303,7 +306,7 @@ מוחק הודעות… מחק עבורי מחק עבור כולם - הודעה זו תימחק לצמיתות עבור כל אחד שנמצא בשיחה. חברי קבוצה יוכלו לראות שמחקת הודעה. + הודעה מקורית לא נמצאה ההודעה המקורית אינה זמינה יותר נכשל בפתיחת הודעה @@ -399,6 +402,8 @@ מטב עבור שירותי Play חסרים מכשיר זה אינו תומך בשירותי Play. הקש כדי להשבית מיטובי סוללה של מערכת שמונעים מ־Signal לאחזר הודעות בעת אי־פעילות. + גרסה זו של Signal פגה. עדכן עכשיו כדי לשלוח ולקבל הודעות. + עדכן עכשיו שתף עם צרופות מרובות נתמכות רק עבור תמונות וסרטונים @@ -540,6 +545,8 @@ בקשות והזמנות של חברי קבוצה הוסף חברי קבוצה ערוך מידע קבוצה + מי יכול להוסיף חברי קבוצה חדשים? + מי יכול לערוך את המידע של קבוצה הזאת? קישור קבוצה חסום קבוצה בטל חסימת קבוצה @@ -573,6 +580,9 @@ נכשל בעדכון הקבוצה עקב שגיאת רשת, אנא נסה שוב מאוחר יותר. ערוך שם ותמונה קבוצה מיושנת + זוהי קבוצה מיושנת. מאפיינים כמו מנהלני קבוצה זמינים רק בקבוצות חדשות. + זוהי קבוצת MMS בלתי מאובטחת. כדי להתכתב באופן פרטי ולהשיג גישה אל מאפיינים כמו שמות קבוצה, הזמן את אנשי הקשר שלך אל Signal. + הזמן עכשיו יידע אותי לגבי אזכורים לקבל התראות כאשר אתה מוזכר בהתכתבויות מושתקות? @@ -673,6 +683,7 @@ הצטרפות אל קבוצה באמצעות קישור אינה נתמכת עדין על ידי Signal. מאפיין זה ישוחרר בעדכון עתידי. הגרסה של Signal שאתה משתמש בה אינה תומכת בקישורי קבוצה. עדכן אל הגרסה האחרונה כדי להצטרף אל קבוצה זו באמצעות קישור. עדכן את Signal + מכשיר מקושר אחד או יותר שלך מריצים גרסה של Signal שאינה תומכת בקישורי קבוצה. עדכן את Signal במכשירים המקושרים שלך כדי להצטרף אל קבוצה זו. קישור הקבוצה בלתי תקף להוסיף את ”%1$s“ אל הקבוצה? @@ -1027,6 +1038,14 @@ ההודעות שלך לא יפוגו. הודעות שנשלחות ומתקבלות בשיחה זו ייעלמו %s אחרי שייראו. + עדכן עכשיו + גרסה זו של Signal תפוג היום. עדכן אל הגרסה האחרונה. + + גרסה זו של Signal תפוג מחר. עדכן אל הגרסה האחרונה. + גרסה זו של Signal תפוג עוד %d ימים. עדכן אל הגרסה האחרונה. + גרסה זו של Signal תפוג עוד %d ימים. עדכן אל הגרסה האחרונה. + גרסה זו של Signal תפוג עוד %d ימים. עדכן אל הגרסה האחרונה. + הכנס משפט־סיסמה צלמית Signal @@ -1106,10 +1125,21 @@ כדי להתקשר אל %1$s, היישום Signal צריך גישה אל המצלמה שלך Signal %1$s מתקשר… + שיחה קבוצתית שיחה קולית של Signal… שיחת וידאו של Signal… + התחל שיחה + שיחה קבוצתית + הצג משתתפים + הוידאו שלך כבוי + + בשיחה זו · איש %1$d + בשיחה זו · %1$d אנשים + בשיחה זו · %1$d אנשים + בשיחה זו · %1$d אנשים + בחר את מדינתך אתה חייב לציין את @@ -2202,9 +2232,11 @@ נזכיר לך מאוחר יותר. אימות PIN יהיה חובה עוד %1$d ימים. אמור אל Signal מה אתה חושב + כדי להפוך את Signal ליישום ההתכתבות הטוב ביותר ביקום, נשמח לשמוע את המשוב שלך. למד עוד בטל מחקר Signal + אנחנו מאמינים בפרטיות.

Signal אינו עוקב אחריך או אוסף את הנתונים שלך. כדי לשפר את Signal עבור כולם, אנחנו מסתמכים על משוב משתמשים, ונשמח לקבל את שלך.

אנחנו מפעילים סקר כדי להבין כיצד אתה משתמש ב־Signal. הסקר שלנו אינו אוסף נתונים כלשהם שיזהו אותך. אם אתה מעוניין בשיתוף של משוב נוסף, תהיה לך אפשרות לספק מידע ליצירת קשר.

אם יש לך כמה דקות ומשוב להציע, נשמח לשמוע ממך.

]]>
ענה על הסקר לא תודה הסקר מאורח על ידי Surveygizmo בתחום המאובטח surveys.signalusers.org @@ -2347,6 +2379,8 @@ שיחת וידאו בלתי מאובטחת שיחת וידאו להסיר את %1$s מתפקיד מנהלן קבוצה? + \"%1$s\" יוכל/תוכל לערוך קבוצה זו ואת חבריה. + להסיר את %1$s מהקבוצה? הסר הועתק ללוח מנהלן @@ -2357,6 +2391,8 @@ מהן קבוצות מיושנות? קבוצות מיושנות הן קבוצות שאינן תואמות עם מאפיינים של קבוצה חדשה כמו מנהלנים ועדכוני קבוצה תיאוריים יותר. האם אני יכול לשדרג קבוצה מיושנת? + קבוצות מיושנות אינן יכולות להשתדרג עדין אל קבוצות חדשות, אבל אתה יכול ליצור קבוצה חדשה עם אותם חברי קבוצה אם יש להם את הגרסה האחרונה של Signal. + Signal יציע דרך לשדרג קבוצות מיושנות בעתיד. שתף באמצעות Signal העתק diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 0263d32cb..4699da880 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -124,10 +124,12 @@ プロフィール画像を消去しますか? グループ写真を削除しますか? - Signalをアップデート - 更新 + Signalをアップデートしてください + このバージョンはサポートされていません。メッセージの送受信を続けるには、最新バージョンにアップデートしてください。 + アップデートする アップデートしない ご注意ください + このバージョンのSignalは期限が切れています。メッセージの履歴は閲覧できますが、アップデートするまで送受信はできません。 ウェブブラウザが見つかりません。 メールアプリがありません。 @@ -274,7 +276,7 @@ メッセージを削除しています… 自分の分だけ削除 全員分を削除 - このメッセージを参加者全員の会話から削除します。参加者からは、あなたがメッセージを削除したことがわかります。 + 元のメッセージが見つかりません 元のメッセージはすでに削除されています メッセージを開けませんでした @@ -358,6 +360,7 @@ Google Play開発者サービスの不在に対応する この端末はGoogle Play開発者サービスをサポートしていません。Signalがいつでもメッセージを受信できるよう、タップしてバッテリー最適化機能を無効にしてください。 + このバージョンのSignalは期限が切れています。メッセージを送受信するには、アップデートしてください。 アップデートする 共有 @@ -404,11 +407,11 @@ これらのユーザをを自動的にこのグループに追加することはできません。\n\nユーザは招待を受け入れるまで、グループメッセージを閲覧できません。 グループを抜けますか? - このグループとの会話が出来なくなりますがよろしいでしょうか。 + このグループでのメッセージ送受信ができなくなります。 抜ける - 新しい管理者を選択 - グループから抜ける前に新しい管理者を最低1人選んでください。 - 管理者を選択 + 新しい管理者を選択してください + グループから抜ける前に、新しい管理者を1人以上選択する必要があります。 + 管理者を選択する 無効にする どんなリンクもプレビュー @@ -479,6 +482,8 @@ メンバー申請と招待 メンバーを追加 グループの情報を編集 + メンバーを追加できる人は? + グループ情報を編集できる人は? グループリンク グループをブロック グループのブロック解除 @@ -506,6 +511,8 @@ ネットワークエラーによりグループを更新できませんでした。のちほど再度試してください。 名前と写真の編集 レガシーグループ + これはレガシーグループです。グループ管理者などの機能は新しいグループでのみ利用可能です。 + これは安全でないMMSグループです。安全にチャットしたりグループ名などの機能を利用するには、連絡先をSignalに招待してください。 招待する メンションの通知 @@ -595,6 +602,7 @@ リンク経由でのグループ参加は、Signalではまだ対応していません。この機能は今後のアップデートで提供されます。 このバージョンのSignalはグループリンクをサポートしていません。リンク経由でこのグループに参加するには、最新バージョンにアップデートしてください。 Signalをアップデート + リンク済み端末で使用中のSignalがグループリンクをサポートしていません。このグループに参加するには、リンク済み端末のSignalをアップデートしてください。 グループリンクが正しくありません 「%1$s」をこのグループに追加しますか? @@ -903,7 +911,11 @@ あなたのメッセージは自動で消去されません。 この会話内で送受信されるメッセージは、閲覧後%sで消えます。 - いまアップデートする + アップデートする + このバージョンのSignalは本日、期限が切れます。最新バージョンにアップデートしてください。 + + このバージョンのSignalは%d日後に期限が切れます。最新バージョンにアップデートしてください。 + パスフレーズを入力してください Signalアイコン @@ -986,11 +998,11 @@ Signalビデオ通話… 通話を開始する グループ通話 - 参加者を見る + 参加者を確認する あなたの映像はオフです - この通話 · %1$d人 + 参加者 · %1$d人 国を選択してください @@ -1395,7 +1407,7 @@ 連絡先の写真 - 読み込み中 + 読み込んでいます 再生 … 停止 ダウンロード @@ -2021,12 +2033,14 @@ あとで再度お知らせします。PINの確認は%1$d日後に必須になります。 Signalへのご提案 + Signalを地球上で最高のメッセージアプリにするため、皆様からのフィードバックをお待ちしています。 詳しく見る キャンセル Signalリサーチ - 調査に参加 - 今はしない - この調査は、安全なドメイン(surveys.signalusers.org)上でSurveygizmoが管理しています。 + 私たちはプライバシーを尊重しています。

Signalはあなたのデータを収集したり追跡することはありません。Signalをより良いものにするために、ユーザの皆様からのフィードバックをお待ちしています。

私たちは、あなたのSignalの使用方法について調査しています。この調査では、あなたを特定するデータを収集することはありません。追加のフィードバックを共有することに興味がある場合は、連絡先情報を提供するオプションがあります。

お時間とご意見をお持ちの方は、ぜひご連絡ください。

]]>
+ 調査に参加する + 参加しない + この調査は、Surveygizmoが安全なドメイン (surveys.signalusers.org) でホストしています。 転送アイコン 読み込んでいます… @@ -2166,6 +2180,7 @@ 安全ではない音声電話 ビデオ通話 グループ管理者から %1$s を削除しますか? + 「%1$s」がこのグループの設定やメンバーを編集できるようになります。 このグループから %1$s を削除しますか? 削除 クリップボードにコピーしました @@ -2177,6 +2192,8 @@ レガシーグループとは? レガシーグループは新グループとは互換性がなく、管理機能や詳細なグループ内の更新情報などの機能を使用できません。 レガシーグループをアップグレードできますか? + レガシーグループは新しいグループにアップグレードできませんが、メンバーが最新バージョンのSignalを使用していれば、同じメンバーで新しいグループを作成できます。 + Signalは将来、レガシーグループをアップグレードする手段を提供します。 Signal経由で共有する コピー diff --git a/app/src/main/res/values-jv/strings.xml b/app/src/main/res/values-jv/strings.xml index fae06334e..473850b57 100644 --- a/app/src/main/res/values-jv/strings.xml +++ b/app/src/main/res/values-jv/strings.xml @@ -239,7 +239,7 @@ SMS Taksih mbrusaki Taksih mbrusaki pesen… - Pesen niki bakal dibrusak kangge piyambak sedaya ing obrolan niki sacara permanen Anggota sedaya bakal saged ningali menawi sampeyan mbrusaki pesen. + Pesen asli mboten saged kepanggih Pesen asli sampun mboten enten malih Gagal mbikak pesen diff --git a/app/src/main/res/values-km/strings.xml b/app/src/main/res/values-km/strings.xml index 8563a4629..c0de44063 100644 --- a/app/src/main/res/values-km/strings.xml +++ b/app/src/main/res/values-km/strings.xml @@ -269,7 +269,7 @@ កំពុងលុបសារ… លុបសម្រាប់ខ្ញុំ លុបសម្រាប់ទាំងអស់គ្នា - សារនេះ នឹងត្រូវលុបចោលរហូតសម្រាប់អ្នកទាំងអស់គ្នាក្នុងការសន្ទនានេះ។ សមាជិក នឹងអាចមើលឃើញ អ្នកបានលុបសារនេះ។ + រកមិនឃើញសារដើម សារដើមលែងមានទៀតហើយ បរាជ័យក្នុងការបើកសារ diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index f1acd61fa..04ab479bf 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -267,7 +267,7 @@ 메시지 삭제 중… 나에게서 삭제 모두에게서 삭제 - 이 메시지가 대화에 참가한 모든 이들에게서 삭제됩니다. 멤버는 내가 메시지를 삭제했다는 것을 볼 수 있습니다. + 원본 메시지를 찾을 수 없음 원본 메시지를 더 이상 볼 수 없음 메시지 열기 실패 diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 46117ee82..9f70ea61a 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -134,8 +134,11 @@ Šalinti grupės nuotrauką? Atnaujinti Signal + Ši programėlės versija daugiau nebepalaikoma. Norėdami ir toliau siųsti bei gauti žinutes, atnaujinkite programėlę iki naujausios versijos. Atnaujinti + Neatnaujinti Įspėjimas + Jūsų Signal versija nebegalioja. Galite žiūrėti žinučių istoriją, tačiau nebegalėsite siųsti ar gauti žinučių tol, kol neatnaujinsite programėlės. Nerasta jokios saityno naršyklės. Nerasta jokios el. pašto programėlės. @@ -303,7 +306,7 @@ Ištrinamos žinutės… Ištrinti man Ištrinti visiems - Ši žinutė šiame pokalbyje bus visam laikui ištrinta visiems žmonėms. Nariai galės matyti, kad ištrynėte žinutę. + Pradinė žinutė nerasta Pradinė žinutė daugiau nebeprieinama Nepavyko atverti žinutės @@ -399,6 +402,8 @@ Optimizuoti trūkstamoms „Play“ paslaugoms Šis įrenginys nepalaiko „Play“ paslaugų. Bakstelėkite, norėdami išjungti sistemos baterijos optimizavimus, kurie neleidžia Signal gauti žinučių tol, kol įrenginys yra neaktyvus. + Ši Signal versija nebegalioja. Norėdami siųsti ir gauti žinutes, atnaujinkite programėlę dabar. + Atnaujinti dabar Bendrinti su Keli priedai yra palaikomi tik paveikslams ir vaizdo įrašams @@ -540,6 +545,8 @@ Narių prašymai ir pakvietimai Pridėti narių Taisyti grupės informaciją + Kas gali pridėti naujus narius? + Kas gali taisyti šios grupės informaciją? Grupės nuoroda Užblokuoti grupę Atblokuoti grupę @@ -573,6 +580,9 @@ Nepavyko atnaujinti grupės dėl tinklo klaidos, vėliau bandykite dar kartą Taisyti pavadinimą ir paveikslą Pasenusi grupė + Tai yra pasenusi grupė. Tokios ypatybės, kaip grupės administratoriai, yra prieinamos tik naujosiose grupėse. + Tai yra nesaugi MMS grupė. Norėdami kalbėtis privačiai ir turėti prieigą prie tokių ypatybių, kaip grupių pavadinimai, pakvieskite savo adresatus į Signal. + Pakviesti dabar Pranešti apie paminėjimus Gauti pranešimus, kai jus pamini pokalbiuose, kuriuose pranešimai yra išjungti? @@ -673,6 +683,7 @@ Signal kol kas nepalaiko prisijungimo prie grupių per nuorodas. Ši ypatybė bus išleista būsimajame atnaujinime. Jūsų naudojama Signal versija nepalaiko grupės nuorodų. Norėdami prisijungti prie šios grupės per nuorodą, atnaujinkite Signal iki naujausios versijos. Atnaujinti Signal + Vienas ar daugiau iš jūsų susietų įrenginių naudoja Signal versiją, kuri nepalaiko grupės nuorodų. Norėdami prisijungti prie šios grupės, atnaujinkite Signal savo susietame įrenginyje(-iuose). Grupės nuoroda negalioja Pridėti „%1$s“ į grupę? @@ -1027,6 +1038,14 @@ Jūsų žinučių galiojimas nesibaigs. Šiame pokalbyje išsiųstos ir gautos žinutės išnyks %s po to, kai jos buvo pamatytos. + Atnaujinti dabar + Šiandien nustos galioti ši Signal versija. Atnaujinkite Signal iki naujausios versijos. + + Rytoj nustos galioti ši Signal versija. Atnaujinkite Signal iki naujausios versijos. + Po %d dienų nustos galioti ši Signal versija. Atnaujinkite Signal iki naujausios versijos. + Po %d dienų nustos galioti ši Signal versija. Atnaujinkite Signal iki naujausios versijos. + Po %d dienos nustos galioti ši Signal versija. Atnaujinkite Signal iki naujausios versijos. + Įveskite slaptafrazę Signal piktograma @@ -1106,10 +1125,21 @@ Norint skambinti %1$s, Signal reikia prieigos prie jūsų kameros Signal %1$s Skambinama… + Grupės skambutis Signal balso skambutis… Signal vaizdo skambutis… + Pradėti skambutį + Grupės skambutis + Rodyti dalyvius + Jūsų vaizdas yra išjungtas + + Šiame skambutyje · %1$d žmogus + Šiame skambutyje · %1$d žmonės + Šiame skambutyje · %1$d žmonių + Šiame skambutyje · %1$d žmogus + Pasirinkite savo šalį Jūs privalote nurodyti savo @@ -1816,8 +1846,8 @@ Gauti visas MMS Naudoti Signal visoms gaunamosioms tekstinėms žinutėms Naudoti Signal visoms gaunamosioms multimedijos žinutėms - Enter (Įvedimo) klavišas išsiunčia - Enter (Įvedimo) klavišo paspaudimas išsiųs tekstines žinutes + „Enter“ (Įvedimo) klavišas išsiunčia + „Enter“ (Įvedimo) klavišo paspaudimas išsiųs tekstines žinutes Generuoti nuorodų peržiūras Gauti savo siunčiamoms žinutėms nuorodų peržiūras tiesiogiai iš internetinių svetainių. Pasirinkti tapatybę @@ -2202,9 +2232,11 @@ Priminsime jums vėliau. PIN kodo patvirtinimas po %1$d dienų taps privalomu. Pasakykite Signal ką galvojate + Norėdami padaryti Signal geriausia susirašinėjimo programėle pasaulyje, norėtume išgirsti jūsų atsiliepimus. Sužinoti daugiau Atmesti Signal tyrimas + Mes tikime privatumu.

Signal neseka jūsų ir nerenka jokių jūsų duomenų. Norėdami visiems patobulinti Signal, remiamės naudotojų atsiliepimais, ir norėtume išgirsti jūsų atsiliepimą.

Mes vykdome apklausą, kad suprastume kaip naudojatės Signal. Apklausoje mes nerenkame jokių duomenų, galinčių jus identifikuoti. Jei pageidaujate pateikti papildomą atsiliepimą, turėsite galimybę pateikti kontaktinę informaciją.

Jei turite kelias minutes ir norėtumėte pateikti atsiliepimą, mes su malonumu jus išklausysime.

]]>
Dalyvauti apklausoje Ne, ačiū Apklausą talpina Surveygizmo, saugioje srityje surveys.signalusers.org @@ -2347,6 +2379,8 @@ Nesaugus balso skambutis Vaizdo skambutis Pašalinti %1$s iš grupės administratorių? + „%1$s“ galės taisyti šią grupę ir jos narių sąrašą. + Pašalinti %1$s iš grupės? Šalinti Nukopijuota į iškarpinę Administratorius @@ -2357,6 +2391,8 @@ Kas yra pasenusios grupės? Pasenusios grupės – tai grupės, kurios yra nesuderinamos su naujųjų grupių ypatybėmis, tokiomis kaip administratoriai ir išsamesni grupių atnaujinimai. Ar galiu naujinti pasenusią grupę? + Pasenusios grupės negali būti naujintos į naująsias grupes, bet galite sukurti naująją grupę su tais pačiais nariais, jeigu jie naudoja naujausią Signal versiją. + Ateityje Signal pateiks būdą kaip naujinti pasenusias grupes į naująsias. Bendrinti per Signal Kopijuoti diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index 87b11473d..bbfa9a756 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -280,7 +280,7 @@ Tiek dzēstas ziņas… Dzēst tikai man Dzēst no visiem - Šī ziņa tiks dzēsta no visiem dalībniekiem. Dalībnieki var redzēt, ka jūs izdzēsāt ziņu. + Oriģinālā ziņa netika atrasta Oriģinālā ziņa vairs nav pieejama Neizdevās atvērt ziņu diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index b6e533059..c5da1ee64 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -281,7 +281,7 @@ സന്ദേശങ്ങൾ ഇല്ലാതാക്കുന്നു… എനിക്കായി ഇല്ലാതാക്കുക എല്ലാവർക്കുമായി ഇല്ലാതാക്കുക - സംഭാഷണത്തിലെ എല്ലാവർക്കുമായി ഈ സന്ദേശം ശാശ്വതമായി ഇല്ലാതാക്കപ്പെടും. നിങ്ങൾ ഒരു സന്ദേശം ഇല്ലാതാക്കിയതായി അംഗങ്ങൾക്ക് കാണാൻ കഴിയും. + യഥാർത്ഥ സന്ദേശം കണ്ടെത്തിയില്ല യഥാർത്ഥ സന്ദേശം മേലിൽ ലഭ്യമല്ല സന്ദേശം തുറക്കുന്നതിൽ പരാജയപ്പെട്ടു diff --git a/app/src/main/res/values-mr/strings.xml b/app/src/main/res/values-mr/strings.xml index 0db277973..ae07b88bc 100644 --- a/app/src/main/res/values-mr/strings.xml +++ b/app/src/main/res/values-mr/strings.xml @@ -280,7 +280,7 @@ संदेश हटवत आहे… माझ्यासाठी हटवा प्रत्येकासाठी हटवा - हा संदेश संभाषणातील प्रत्येकासाठी कायमचा हटविला जाईल. आपण संदेश हटविला हे सदस्य पाहण्यास सक्षम असतील. + मूळ संदेश आढळला नाही मूळ संदेश आता उपलब्ध नाही संदेश उघडण्यात अयशस्वी diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index 71ab24527..7231f4078 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -280,7 +280,7 @@ Sletter meldinger… Slett for meg Slett for alle - Denne meldingen vil bli slettet permanent for alle i samtalen. Medlemmer vil se at du slettet en melding. + Opprinnelig melding ikke funnet Opprinnelig melding er ikke lenger tilgjengelig Kunne ikke åpne meldingen diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 1297f7b58..95f2c99f0 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -286,7 +286,7 @@ Berichten aan het wissen … Voor mij wissen Wissen voor iedereen - Dit bericht zal worden verwijderd voor alle deelnemers aan het gesprek. Gespreksdeelnemers zullen wel kunnen zien dat jij een bericht hebt verwijderd. + Oorspronkelijk bericht niet gevonden Het oorspronkelijke bericht is niet langer beschikbaar Openen van bericht is mislukt @@ -909,7 +909,7 @@ Deblokkeren Wil je berichten van %1$s ontvangen, en sta je toe dat hij of zij je profielnaam en -foto kan zien? Hij of zij zal totdat je de gespreksuitnodiging aanvaardt niet kunnen zien dat je zijn of haar berichten hebt gelezen. Wil je berichten van %1$s ontvangen, en sta je toe dat hij of zij je profielnaam en -foto kan zien? Je zult geen berichten ontvangen en je profiel zal voor hem of haar niet meer worden bijgewerkt totdat je hem of haar hebt gedeblokkeerd. - Wil je lid worden van deze groep, en sta je toe dat alle leden van de groep je profielnaam en -foto kunnen zien? De leden van deze groep zullen niet kunnen zien dat je hun berichten hebt gelezen totdat je de uitnodiging hebt aanvaard. + Wil je lid worden van deze groep, en sta je toe dat alle leden van de groep je profielnaam en -foto kunnen zien? Wil je weer berichten van deze groep ontvangen, en sta je toe dat alle leden van de groep je profielnaam en -foto kunnen zien? Je zult geen berichten ontvangen en je profiel zal voor de leden van deze groep niet worden bijgewerkt totdat je de groep hebt gedeblokkeerd. Lid van %1$s Lid van %1$s en %2$s diff --git a/app/src/main/res/values-nn/strings.xml b/app/src/main/res/values-nn/strings.xml index 7ef09dc8b..7b224913b 100644 --- a/app/src/main/res/values-nn/strings.xml +++ b/app/src/main/res/values-nn/strings.xml @@ -286,7 +286,7 @@ Slettar meldingar … Slett for meg Slett for alle - Denne meldinga blir permanent sletta for alle i samtalen. Medlem vil sjå at du sletta ei melding. + Fann ikkje den opphavlege meldinga Den opphavlege meldinga er ikkje lenger tilgjengeleg Klarte ikkje opna meldinga diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index 618b20c3d..5ed41aaf2 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -274,7 +274,7 @@ ਸੁਨੇਹਿਆਂ ਨੂੰ ਮਿਟਾਇਆ ਜਾ ਰਿਹਾ ਹੈ ਮੇਰੇ ਲਈ ਮਿਟਾਓ ਹਰ ਕਿਸੇ ਲਈ ਮਿਟਾਓ - ਇਹ ਸੰਦੇਸ਼ ਗੱਲਬਾਤ ਵਿਚਲੇ ਹਰੇਕ ਲਈ ਸਥਾਈ ਤੌਰ ਤੇ ਮਿਟਾ ਦਿੱਤਾ ਜਾਏਗਾ. ਮੈਂਬਰ ਇਹ ਵੇਖਣ ਦੇ ਯੋਗ ਹੋਣਗੇ ਕਿ ਤੁਸੀਂ ਇੱਕ ਸੁਨੇਹਾ ਮਿਟਾ ਦਿੱਤਾ ਹੈ. + ਅਸਲੀ ਸੁਨੇਹਾ ਨਹੀਂ ਲੱਭਿਆ ਮੂਲ ਸੁਨੇਹਾ ਹੁਣ ਉਪਲਬਧ ਨਹੀਂ ਹੈ ਸੁਨੇਹਾ ਖੋਲ੍ਹਣ ਵਿੱਚ ਅਸਫਲ diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 7d442d36e..af23531bb 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -306,7 +306,7 @@ Usuwanie wiadomości… Usuń u mnie Usuń u wszystkich - Ta wiadomość zostanie trwale usunięta u wszystkich, uczestników tej konwersacji. Będą oni mogli zobaczyć, że skasowałeś(aś) wiadomość. + Nie znaleziono oryginalnej wiadomości Oryginalna wiadomość nie jest już dostępna Nie udało się otworzyć wiadomości diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 8b25f11b4..63f0400df 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -128,8 +128,11 @@ Remover a foto do grupo? Atualizar o Signal + Esta versão do aplicativo já não é suportada. Para poder enviar e receber as mensagens, atualize para a versão mais recente. Atualizar + Não atualizar Cuidado + A sua versão do Signal não é mais suportada. Ainda pode ver seu histórico de mensagens mas não poderá enviar ou receber mensagens até que atualize o Signal. Nenhum navegador encontrado. Nenhum email app encontrado @@ -283,7 +286,7 @@ Excluindo mensagens… Excluir para mim Excluir para todas as pessoas - Essa mensagem será definitivamente excluída da conversa para todas as pessoas. Os membros verão que você excluiu a mensagem. + Mensagem original não encontrada Mensagem original indisponível Falha ao abrir mensagem @@ -371,6 +374,8 @@ Otimizar para ausência do Play Services Este dispositivo não tem suporte para o Play Services. Clique para desabilitar as otimizações de bateria do sistema que impedem o Signal de receber mensagens enquanto está inativo. + Esta versão do Signal não é mais suportada. Atualize agora para poder enviar e receber as mensagens. + Atualize agora Encaminhar para Muitos anexos de uma só vez são suportados apenas para imagens e vídeos. @@ -425,7 +430,7 @@ Desabilitar Pré-visualizar qualquer link - Agora você pode carregar pre-visualizações dos links diretamente de qualquer site nos mensagens que você envia. + Agora você pode carregar pré-visualizações dos links diretamente de qualquer site nas mensagens que você envia. Não há pré-visualização de link disponível Este link de grupo não está ativo @@ -498,6 +503,8 @@ Pedidos de membros & convites Adicionar membros Editar informações do grupo + Quem pode adicionar os novos membros? + Quem pode editar as informações deste grupo? O link de grupo Bloquear o grupo Desbloquear o grupo @@ -527,6 +534,9 @@ Falha ao atualizar o grupo devido a um erro da rede, favor tentar novamente mais tarde Editar nome e foto Grupo Legado + Este é um Grupo Legado. Recursos como administradores de grupos só estão disponíveis em Novos Grupos. + Este é um Grupo MMS inseguro. Para conversar em privado e aceder os recursos como nomes de grupos, convide seus contactos para usar o Signal. + Convide agora Notifique-me ao ser Mencionado Quer receber as notificações quando você for mencionado nas conversas silenciadas? @@ -558,7 +568,7 @@ Editar nome e foto Mensagem Chamada de voz - Chamada de voz sem segurança + Chamada de voz sem segurança Chamada de vídeo %1$s convidou 1 pessoa @@ -619,6 +629,7 @@ Participar no grupo através de um link ainda não é suportado pelo Signal. Este recurso será lançado na próxima atualização. A versão do Signal que você está usando não suporta links de grupo. Atualize para a versão mais recente para se conectar a este grupo via link. Atualizar o Signal + Um ou mais de seus dispositivos vinculados estão usando uma versão do Signal que não suporta as chamadas de grupo. Actualize o Signal no(s) seu(s) dispositivo(s) vinculado(s) para participar neste grupo. O link de grupo não é válido Adicionar “%1$s” ao grupo? @@ -782,7 +793,7 @@ %1$s adicionou %2$s. %1$s adicionou você ao grupo. Você entrou no grupo. - %1$s entraram no grupo. + %1$s entraram no grupo. Você excluiu %1$s. %1$s excluiu %2$s. @@ -903,8 +914,8 @@ Membro de %1$s e %2$s Membro de %1$s, %2$s e %3$s - %1$dmembro - %1$dmembros + %1$d membro + %1$d membros %1$d membro (+%2$d convidados) @@ -943,6 +954,12 @@ Suas mensagens não irão expirar. Mensagens enviadas e recebidas nesta conversa irão desaparecer %s após terem sido visualizadas. + Atualize agora + Esta versão do Signal perderá suporte hoje. Atualize para a versão mais recente. + + Esta versão do Signal perderá suporte amanhã. Atualize para a versão mais recente. + Esta versão do Signal perderá suporte dentro de %d dias. Atualize para a versão mais recente. + Inserir frase-chave Ícone do Signal @@ -1020,10 +1037,19 @@ Para chamar %1$s, Signal precisa ter acesso à sua câmera Signal %1$s Chamando… + Chamada em Grupo Mensagem de voz Signal… Chamada de vídeo Signal… + Começar a Chamada + Chamada em Grupo + Ver os participantes + Seu vídeo está desligado + + Nesta chamada · %1$d pessoa + Nesta chamada · %1$d pessoas + Selecione seu país Você deve especificar o @@ -1178,7 +1204,7 @@ Você marcou como não verificado A mensagem não pôde ser processada Solicitação de mensagem - %1$sfoi adicionado ao grupo + %1$s adicionou você ao grupo %1$s convidou você para o grupo Foto GIF @@ -1286,7 +1312,7 @@ Reagiu com %1$s ao seu vídeo efêmero. Reagiu com %1$s à sua figurinha. Essa mensagem foi excluída. - Desativar Contato Juntou-se ao Signal Notificação? Você pode activá-las novamente em Signal > Configurações > Notificações. + Desativar notificações de contatos que entram no Signal? Você pode ativá-las novamente em Signal > Configurações > Notificações. Padrão Ligações @@ -1997,7 +2023,7 @@ O PIN deve conter pelo menos %1$d caracteres - O PIN deve conter pelo menos%1$d dígito + O PIN deve conter pelo menos %1$d dígito O PIN deve conter pelo menos %1$d dígitos Criar um novo PIN @@ -2080,12 +2106,14 @@ Nós vamos te lembrar depois. A confirmação do seu PIN será obrigatória em %1$d dias. Diga ao Signal o que você pensa + Para fazer do Signal o melhor aplicativo de mensagens do planeta, gostaríamos de receber seu feedback. Saiba mais Ignorar Pesquisa do Signal + Acreditamos na privacidade.

O Signal não o rastreia nem colecta seus dados. Para melhorar o Signal para todos, contamos com um feedback, e nós adoraríamos o seu.

Estamos fazendo pesquisas para compreender como vocês utilizam o Signal. A nossa pesquisa não colecta nenhum dado que o pode identificar. Se estiver interessado em compartilhar um feedback adicional, terá a opção de fornecer informações de contacto.

Se tiver alguns minutos e um feedback para oferecer, gostaríamos de ouvir-lo.

]]>
Participar em pesquisa Não, obrigado - A pesquisa é hospedada pela Surveygizmo no domínio seguro surveys.signalusers.org + A pesquisa é hospedada pela Surveygizmo no domínio seguro surveys.signalusers.org Ícone do transporte Carregando… @@ -2160,7 +2188,7 @@ Lembretes do PIN Os PINs mantêm as informações armazenadas no Signal criptografadas para que somente você possa acessá-las. Seu perfil, configurações e contatos serão restaurados quando você reinstalar o Signal. Adicione segurança extra exigindo seu PIN do Signal para registrar seu número de telefone com ele novamente. - Os lembretes ajudam você a memorizar seu PIN, já que não pode ser recuperado. Você será solicitado com menos freqüência ao longo do tempo. + Os lembretes ajudam você a memorizar seu PIN, já que não pode ser recuperado. Você será solicitado com menos frequência ao longo do tempo. Desativar Confirme o PIN Confirme o seu PIN do Signal @@ -2225,6 +2253,8 @@ Chamada de voz sem segurança Chamada de vídeo Remover %1$s como administrador do grupo? + \"%1$s\" poderá editar este grupo e os seus membros. + Excluir %1$s do grupo? Remover Copiado para a área de transferência Admin @@ -2233,8 +2263,10 @@ Legado vs Novos Grupos O que são Grupos Legado? - Grupos Legado são grupos que não são compatíveis com recursos de Novos Grupos como administração e atualizações de grupo mais descritivas. + Grupos Legado são grupos que não são compatíveis com recursos de Novos Grupos como administração e atualizações de grupo mais descritivas. Posso atualizar um Grupo Legado? + Os Grupos Legado ainda não podem ser atualizados para os Novos Grupos, mas é possível criar um Novo Grupo com os mesmos membros se estiverem usando a última versão do Signal. + O Signal oferecerá uma forma de atualizar os Grupos Legado no futuro. Compartilhar via Signal Copiar diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index c4a586702..638dbc765 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -132,6 +132,7 @@ Atualizar Não atualizar Aviso + A sua versão do Signal expirou. Pode ver o seu histórico de mensagens mas não será capaz de enviar ou receber mensagens até que atualize. Não foi encontrado nenhum navegador de Internet. Não foi encontrada nenhuma aplicação de e-mail. @@ -285,7 +286,7 @@ A eliminar mensagens… Eliminar para mim Eliminar para todos - Esta mensagem será eliminada para todos os presentes na conversa. Os membros serão capazes de ver que você eliminou a mensagem. + Não foi encontrada a mensagem original A mensagem original já não se encontra disponível Falha ao abrir a mensagem @@ -373,6 +374,7 @@ Otimizar para os \"Play Services\" em falta. Este dispositivo não suporta os Play Services. Toque para desativar o sistema de otimização de bateria que impede o Signal de recolher mensagens enquanto inativo. + Esta versão do Signal expirou. Faça agora a atualização par aenviar e receber mensagens. Atualizar agora Partilhar com @@ -501,6 +503,8 @@ Pedidos e convites para membro Adicionar membros Editar informação do grupo + Quem pode adicionar novos membros? + Quem pode editar a informação do grupo? Link do grupo Bloquear grupo Desbloquear grupo @@ -530,6 +534,9 @@ Ocorreu um erro ao tentar atualizar o grupo devido a um erro de rede. Por favor, tente novamente mais tarde Editar nome e imagem Grupo legado + Este é um \'Grupo legado\'. Recursos como grupos de administradores apenas se encontram disponíveis para \'Grupos novos\'. + Este é um Grupo MMS inseguro. Para conversar de forma privada e aceder a recursos como nomes do grupo, convide os seus contactos para o Signal. + Convidar agora Notificar-me quando for \'Mencionado\' Receber notificações quando for mencionado em chats silenciados? @@ -622,6 +629,7 @@ Entrar para um grupo através de um link ainda não é suportado pelo Signal. Este recurso será lançado numa atualização futura. A versão do Signal que está a utilizar não suporta links de grupo. Atualize para a última versão de forma a poder entrar para este grupo através do link do grupo. Atualizar o Signal + Um ou mias dos seus dispositivos ligados estão a correr uma versão do Signal que não suporta ligações para grupos. Atualize o Signal nos seus dispositivos ligados para entrar para este grupo. Link do grupo inválido Adicionar “%1$s” ao grupo? @@ -946,6 +954,11 @@ As mensagens enviadas e recebidas nesta conversa irão ser destruídas %s após serem vistas. Atualizar agora + Esta versão do Signal expira hoje. Atualize para a versão mais recente. + + Esta versão do Signal expira amanhã. Atualize para a versão mais recente. + Esta versão do Signal irá expirar dentro de %d dias. Atualize para a versão mais recente. + Insira a frase-passe Ícone do Signal @@ -1023,10 +1036,19 @@ Para telefonar a %1$s, o Signal necessita de aceder à sua câmara Chamada Signal %1$s A chamar… + Chamada de grupo Chamada de voz do Signal… Chamada de vídeo do Signal… + Iniciar chamada + Chamada de grupo + Ver participantes + O seu vídeo encontra-se desligado + + Nesta chamada · %1$d pessoa + Nesta chamada · %1$d pessoas + Escolha o seu país Deve escolher o @@ -2080,9 +2102,11 @@ Mensagem de troca de chaves inválida para esta versão do protocolo. Iremos lembrá-lo(a) mais tarde. Confirmar o seu PIN será obrigatório dentro de %1$d dias. Diga ao Signal o que acha + Para poder transformar o Signal na melhor aplicação de mensagens do planeta, gostaríamos de obter o seu feedback. Saber mais Ignorar Pesquisa Signal + Acreditamos na privacidade.

O Signal não rastreia ou recolhe os seus dados. Para melhorar o Signal para todos, dependemos do feedback dos seus utilizadores, e nós adoramos os seus.

Estamos a lançar um questionário para perceber como é que utiliza o Signal. O nosso questionário não recolhe nenhuns dados que o possam identificar. Se está interessado em partilhar o seu feedback adicional tem a opção para fornecer a informação de contacto.

Se tiver alguns minutos e feedback para fornecer iríamos adorar ouvi-lo.

]]>
Preencher o questionário Não, obrigado O questionário está alojado pela Surveygizmo no domínio seguro surveys.signalusers.org @@ -2225,6 +2249,8 @@ Mensagem de troca de chaves inválida para esta versão do protocolo. Chamada de voz insegura Vídeochamada Remover %1$s de administrador de grupo? + \"%1$s\" será capaz de editar este grupo e os seus membros. + Remover %1$s deste grupo? Remover Copiado para a área de transferência Administrador @@ -2235,6 +2261,8 @@ Mensagem de troca de chaves inválida para esta versão do protocolo. O que são \'Grupos legados\'? Os \'Grupos legados\' são grupos que não são compatíveis com os recursos de \'Grupo novo\' tais como administradores e atualizações mais descritivas do grupo. Posso fazer o upgrade para um \'Grupo legado\'? + OS \'Grupos legados\' não podem ser convertidos em \'Grupos novos\', mas você pode criar um \'Grupo novo\' com os mesmos membros caso eles tenham a última versão do Signal. + O Signal irá oferecer futuramente uma forma de atualizar os \'Grupos legados\'. Partilhar através do Signal Copiar diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 38095587f..090ae3648 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -131,8 +131,11 @@ Eliminați poza de grup? Actualizați Signal + Această versiune a aplicației nu mai este suportată. Pentru a continua să trimiteți și să primiți mesaje, actualizați la cea mai recentă versiune. Actualizează + Nu actualizați Avertizare + Versiunea dvs. de Signal a expirat. Puteți vizualiza istoricul mesajelor dvs., dar nu veți mai putea trimite sau primi mesaje până nu actualizați. Nu s-a găsit nici un web browser. Nu a fost găsită nici o aplicație de email. @@ -293,7 +296,7 @@ Se șterg mesajele… Șterge doar pentru mine Șterge pentru toată lumea - Acest mesaj va fi șters definitiv pentru toți cei din conversație. Membrii vor putea vedea că ați șters un mesaj. + Mesajul original nu a fost găsit Mesajul original nu mai este disponibil Mesajul nu a putut fi deschis @@ -385,6 +388,8 @@ Optimizează pentru lipsa serviciilor Play Acest dispozitiv nu suportă serviciile Play. Apasă pentru a dezactiva optimizarea de sistem a bateriei care împiedică Signal să preia mesajele cât timp este inactivă. + Această versiune a Signal a expirat. Actualizați acum pentru a trimite și primi mesaje. + Actualizează acum Distribuie cu Atașamentele multiple sunt suportate doar pentru imagini și videouri @@ -519,6 +524,8 @@ Solicitări și invitații ale membrilor Adăugați membri Editare informații despre grup + Cine poate adăuga membrii? + Cine poate edita informațiile grupului? Link grup Blocați grupul Deblocare grup @@ -550,6 +557,9 @@ Grupul nu a putut fi actualizat din cauza unei erori de rețea, vă rugăm să încercați mai târziu Editare nume și poză Grup vechi + Acesta este un grup vechi. Funcții precum administratorii de grup sunt disponibile doar grupurilor noi. + Acesta este un grup MMS nesigur. Pentru a comunica în mod privat și a accesa funcții precum numele de grupuri, invitați-vă contactele la Signal. + Invită acum Anunță-mă pentru mențiuni Doriți să primiți notificări atunci când sunteți menționat în convorbiri fără sunet? @@ -646,6 +656,7 @@ Înscrierea la un grup printr-un link nu este încă suportată de Signal. Această funcționalitate va fi lansată într-o actualizare viitoare. Versiune de Signal pe care o folosiți nu suportă link-uri către grupuri. Actualizați la ultima versine pentru a te alătura acestui grup printr-un link. Actualizați Signal + Unul sau mai multe dintre dispozitivele asociate rulează o versiune de Signal care nu acceptă link-uri către grup. Actualizați Signal pe dispozitivele asociate pentru a vă alătura acestui grup. Linkul de grup nu este valid Adăugați pe “%1$s” la grup? @@ -985,6 +996,13 @@ Mesajele dvs. nu vor expira. Mesajele trimise și primite în această conversație vor dispărea după %s după ce au fost vizualizate. + Actualizează acum + Această versiune de Signal va expira astăzi. Actualizați la cea mai recentă versiune. + + Această versiune de Signal va expira mâine. Actualizați la cea mai recentă versiune. + Această versiune de Signal va expira în %d zile. Actualizați la cea mai recentă versiune. + Această versiune de Signal va expira în %d zile. Actualizați la cea mai recentă versiune. + Introduceți parola Pictogramă Signal @@ -1063,10 +1081,20 @@ Pentru a apela pe %1$s, Signal are nevoie de acces la camera dvs. Signal %1$s Apel în curs… + Apel de grup Apel vocal Signal… Apel video Signal… + Începeți apelul + Apel de grup + Vizualizați participanții + Video-ul dvs. este oprit + + În acest apel · %1$d persoană + În acest apel · %1$d persoane + În acest apel · %1$d persoane + Alegeți ţara dvs. Trebuie să introduceți @@ -2138,11 +2166,14 @@ Am primit mesajul conform căruia schimbul de chei a avut loc pentru o versiune Vă amintim mai târziu. Confirmarea PIN-ului dvs. o să devină obligatorie în %1$d zile. Spune-ne ce părere ai + Pentru a putea să facem din Signal cea mai bună aplicație de mesagerie de pe planetă, ne-ar plăcea să-ți auzim părerea. Aflați mai multe Revocare Cercetare Signal + Credem în confidențialitate.

Signal nu vă urmărește și nu vă colectează informațiile. Pentru a îmbunătăți aplicația pentru toată lumea, ne bazăm pe opiniile utilizatorilor noștri, și ne-ar plăcea să o auzim pe a dvs.

Derulăm un sondaj pentru a înțelege mai bine cum folosiți Signal. Studiul nostru nu colectează informații care v-ar putea identifica. Dacă sunteți interesat în a împărtăși păreri suplimentare cu noi, veți avea opțiunea să ne furnizați informații de contact.

Dacă aveți câteva minute la dispoziție și păreri de oferit, ne-ar plăcea să aflăm de la dvs.

]]>
Completează studiul Nu, mulţumesc + Acest studiu este găzduit de Surveygizmo pe domeniul securizat surveys.signalusers.org Pictogramă transport Se încarcă… @@ -2282,6 +2313,8 @@ Am primit mesajul conform căruia schimbul de chei a avut loc pentru o versiune Apel vocal nesecurizat Apel video Eliminați pe %1$s ca și admin de grup? + \"%1$s\" va putea edita acest grup și membrii acestuia. + Eliminați pe %1$s din grup? Eliminați S-a copiat în clipboard Admin @@ -2292,6 +2325,8 @@ Am primit mesajul conform căruia schimbul de chei a avut loc pentru o versiune Ce sunt grupurile vechi? Grupurile vechi sunt grupuri care nu sunt compatibile cu funcționalitățiile grupurilor noi, cum ar fi administratorii și actualizări mai descriptive ale grupului. Pot să migrez un grup vechi? + Grupurile vechi nu pot fi încă migrate la cele noi, dar poți crea un Grup Nou cu aceeași membri dacă aceștia folosesc toți ultima versiune de Signal. + Signal va oferi în viitor o modalitate de actualizare a grupurilor vechi. Partajează prin Signal Copy diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index e01d8e140..ff5015888 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -306,7 +306,7 @@ Удаляем сообщения… Удалить для меня Удалить для всех - Это сообщение будет навсегда удалено для всех в этом разговоре. Участники смогут увидеть, что вы удалили сообщение. + Исходное сообщение не найдено Исходное сообщение больше не доступно Не удалось открыть сообщение diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 918253e4a..948e97ea6 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -295,7 +295,7 @@ Mažú sa správy… Zmazať pre mňa Zmazať pre všetkých - Táto správa sa natrvalo vymaže pre všetkých v tejto konverzácii. Členovia budú vidieť, že ste vymazali správu. + Pôvodná správa sa nenašla Pôvodná správa už nie je k dispozícii Nepodarilo sa otvoriť správu diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index 85da4287a..986090d03 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -134,8 +134,11 @@ Želite odstraniti fotografijo skupine? Posodobite aplikacijo Signal + Ta različica aplikacije Signal ni več podprta. Za nadaljnje pošiljanje in prejemanje sporočil jo morate nadgraditi. Posodobitev + Ne nadgradi Opozorilo + Različica aplikacije Signal na vaši napravi je potekla. Še vedno lahko pregledujete zgodovino svojih sporočil, ne morete pa jih pošiljati ali prejemati dokler je ne nadgradite. Spletnega brskalnika ni bilo mogoče najti. Nismo našli nobene aplikacije za e-pošto. @@ -303,7 +306,7 @@ Izbris sporočil … Izbriši zame Izbriši za vse - To sporočilo bo za vedno izbrisano za vse udeležene v pogovoru. Člani/ce skupine bodo lahko videli/e, da ste izbrisali sporočilo. + Izvorno sporočilo ni bilo najdeno Izvorno sporočilo ni več na voljo Odpiranje sporočila ni uspelo @@ -399,6 +402,8 @@ Optimiziraj za manjkajočo aplikacijo Play Services Naprava ne podpira storitev Play Services. Tapnite za izklop optimizacije baterije, ki aplikaciji Signal preprečuje povezavo v času neaktivnosti naprave. + Ta različica aplikacije Signal je potekla. Posodobite jo, da boste lahko spet pošiljali in prejemali sporočila. + Posodobi zdaj Deli z Več priponk je možnih le pri slikah in videu @@ -540,6 +545,8 @@ Vabila in prošnje za članstvo Dodaj člane/ice Uredi podatke o skupini + Kdo lahko dodaja nove člane? + Kdo lahko ureja informacije o skupini Povezava do skupine Blokiraj skupino Odblokiraj skupino @@ -573,6 +580,9 @@ Posodobitev skupine ni uspela zaradi omrežne napake, poskusite znova kasneje Uredi ime in sliko Stara oblika skupine + To je Stara skupina. Funkcije, kot so skrbniki skupin, so na voljo le pri Novih skupinah. + To je nezavarovana skupina MMS. Za bolj zasebno komunikacijo in napredne funkcije, kot so imena skupin, povabite svoje stike na Signal. + Povabi zdaj Obveščajte me o omembah Želite prejemati obvestila kadar bo vaše ime omenjeno v pogovorih, ki ste jih utišali? @@ -673,6 +683,7 @@ Pridružitev skupini prek povezave v aplikaciji Signal še ni mogoča. Dodana bo v eni prihodnjih posodobitev. Različica aplikacije Signal, ki jo uporabljate, ne podpira povezav do skupine. Za priključitev k skupini preko povezave jo morate nadgraditi. Posodobite aplikacijo Signal + Na eni izmed vaših povezanih naprav je Signal, ki ne podpira povezav do skupine. Za pridružitev k skupini nadgradite aplikacijo Signal na svojih povezanih napravah. Povezava do skupine ni veljavna Želite dodati uporabnika/co“%1$s” v skupino? @@ -1026,6 +1037,14 @@ Vaša sporočila ne bodo potekla. Sporočila, poslana ali prejeta v tem pogovoru, bodo izginila %s po ogledu. + Posodobi zdaj + Ta različica aplikacije Signal poteče danes. Nadgradite je na najnovejšo različico. + + Ta različica aplikacije Signal poteče jutri. Nadgradite je na najnovejšo različico. + Ta različica aplikacije Signal poteče čez %d dni. Nadgradite je na najnovejšo različico. + Ta različica aplikacije Signal poteče čez %d dni. Nadgradite je na najnovejšo različico. + Ta različica aplikacije Signal poteče čez %d dni. Nadgradite je na najnovejšo različico. + Vnesite geslo Ikona Signal @@ -1105,10 +1124,21 @@ Za klic uporabnika/ce %1$s, potrebuje Signal dostop do kamere Signal %1$s Klicanje … + Skupinski klic Zvočni klic Signal … Video klic Signal … + Začni klic + Skupinski klic + Prikaži sodelujoče + Vaš video je izključen + + Na tem klicu · %1$d človek + Na tem klicu · %1$d človeka + Na tem klicu · %1$d ljudje + Na tem klicu · %1$d ljudi + Izberite svojo državo Navesti morate mednarodno kodo svoje države @@ -2192,9 +2222,15 @@ Prejeto sporočilo za izmenjavo ključev za napačno različico protokola.Spomnili vas bomo kasneje. Uporaba PINa postala obvezna čez %1$d dni.
Spomnili vas bomo kasneje. Potrditev PINa bo obvezna čez %1$d dni. + Sporočite Signalu svoje mnenje + Da bi bil Signal najboljša aplikacije za sporočanje v vesolju, pa tudi širše, bi radi slišali vaše mnenje. Več o funkciji zakriti pošiljatelj Prekini + Raziskava Signal + Verjamemo v zasebno komunikacijo.

Signal vam ne sledi in ne zbira vaših podatkov. Za izboljšave aplikacije se zanašamo na vaše povratne informacije, ki jih zelo cenimo.

Zato delamo raziskavo, da bi ugotovili, kako uporabljate aplikacijo Signal. Raziskava ne zahteva od vas nobenih podatkov, po katerih bi vas lahko prepoznali. Če pa bi radi z nami delili dodatne informacije, nam lahko tudi posredujete svoj kontakt.

Če imate nekaj minut časa in izdelano mnenje o nas, vam bomo z veseljem prisluhnili.

]]>
+ Izpolnite anketo Ne, hvala! + Anketa gostuje pri Surveygizmo na domain surveys.signalusers.org Ikona transporta Nalaganje … @@ -2334,6 +2370,8 @@ Prejeto sporočilo za izmenjavo ključev za napačno različico protokola.Nezavarovan zvočni klic
Video klic Odstranim uporabnika/co %1$s kot skrbnika/co skupine? + Uporabnik/ca %1$s bo lahko urejal/a skupino in njeno članstvo. + Želite odstraniti uporabnika/co %1$s iz skupine? Odstrani Kopirano v odložišče Skrbnik @@ -2343,6 +2381,9 @@ Prejeto sporočilo za izmenjavo ključev za napačno različico protokola.Stare in Nove skupine
Kaj so stare skupine? Stare skupine so skupine, ki niso združljive z naprednimi funkcijami Novih skupin, kot so skrbniki in bolj natančno upravljanje. + Ali lahko nadgradim Staro skupino v Novo? + Stare skupine zaenkrat ne morejo biti pretvorjene v Nove, lahko pa ustvarite Novo skupino z identičnim članstvom, če vaši sogovorniki uporabljajo najnovejšo različico aplikacije Signal. + V prihodnosti bo ponujena tudi možnost nadgradnje Starih v Nove skupine Signal. Deli preko Signala Kopiraj diff --git a/app/src/main/res/values-sq/strings.xml b/app/src/main/res/values-sq/strings.xml index b4823a59d..da02a74b8 100644 --- a/app/src/main/res/values-sq/strings.xml +++ b/app/src/main/res/values-sq/strings.xml @@ -286,7 +286,7 @@ Po fshihen mesazhet… Fshije për mua Fshije për këdo - Ky mesazh do të fshihet përgjithmonë për këdo te biseda. Anëtarët do të jenë në gjendje të shohin që ju fshitë një mesazh. + S\’u gjet mesazhi origjinal Mesazhi origjinal s\’gjendet më S’u arrit të hapet mesazhe diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index e3ab7d683..95a25fc7f 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -286,7 +286,7 @@ Tar bort meddelanden… Ta bort för mig Ta bort för alla - Detta meddelande tas bort permanent för alla i konversationen. Medlemmar kan se att du har tagit bort ett meddelande. + Hittade inte originalmeddelandet. Originalmeddelandet inte längre tillgänglig. Det gick inte att öppna meddelandet diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index cd61b8eaf..2fd57b263 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -283,7 +283,7 @@ செய்திகள் நீக்கப்படுகிறது… எனக்கு மட்டும் நீக்கு அனைவருக்கும் நீக்கு - உரையாடலில் உள்ள அனைவருக்கும் இந்த செய்தி நிரந்தரமாக நீக்கப்படும். நீங்கள் ஒரு செய்தியை நீக்கியதை உறுப்பினர்களால் பார்க்க முடியும். + அசல் செய்தி கிடைக்கவில்லை அசல் செய்தி இனி கிடப்பில் இல்லை   செய்தியைத் திறப்பதில் தோல்வி diff --git a/app/src/main/res/values-tl/strings.xml b/app/src/main/res/values-tl/strings.xml index e2a0de030..2201e9487 100644 --- a/app/src/main/res/values-tl/strings.xml +++ b/app/src/main/res/values-tl/strings.xml @@ -261,7 +261,7 @@ SMS Binubura Binubura ang mga mensahe… - Ang mensaheng ito ay permanenterng mabubura para sa lahat na kalahok ng pag-uusap. Makikita ng mga kasapi na binura mo ang mensahe. + Ang orihinal na mensahe ay hindi mahanap Ang orihinal na mensahe ay wala na Nabigong buksan ang mensahe diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 335426f0f..a079e1e2f 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -15,13 +15,13 @@ Şu anda: %s Henüz bir parola belirlemediniz! - Sohbet başına %d ileti - Sohbet başına %d ileti + Konuşma başına %d ileti + Konuşma başına %d ileti Eski iletilerin hepsini şimdi sil? - Bu işlem tüm sohbetleri en yeni iletiye kadar kırpar. - Bu işlem tüm sohbetleri en yeni %d iletiye kadar kırpar. + Bu işlem tüm konuşmaları en yeni iletiye kadar kırpar. + Bu işlem tüm konuşmaları en yeni %d iletiye kadar kırpar. Sil Parolayı devre dışı bırak? @@ -48,6 +48,7 @@ PIN oluşturuldu. PIN devre dışı. Gizle + Hatırlatıcıyı gizle? %d dakika @@ -114,7 +115,7 @@ Son kişiler Signal kişileri Signal grupları - En fazla %d sohbetle paylaşabilirsiniz. + En fazla %d konuşmayla paylaşabilirsiniz. Signal alıcılarını seçin Hiç Signal kişiniz yok Kamera tuşunu yalnızca Signal kişilerine fotoğraf göndermek için kullanabilirsiniz. @@ -127,7 +128,11 @@ Grup fotoğrafı kaldırılsın mı? Signal\'i Güncelle + Uygulamanın bu sürümü artık desteklenmiyor. İleti göndermeye ve almaya devam etmek için en son sürüme güncelleyin. Güncelle + Güncelleme + Uyarı + Sinyal sürümünüz kullanım dışı kaldı. İleti geçmişinizi görüntüleyebilirsiniz, ancak güncelleyene kadar ileti gönderemez veya alamazsınız. Hiçbir web tarayıcısı bulunamadı. Eposta uygulaması bulunamadı. @@ -143,7 +148,7 @@ Bu kişiyle güvenlik numaranızı doğrulamak isteyebilirsiniz. Kabul et - Son sohbetler + Son konuşmalar Kişiler Gruplar Telefon numarasıyla arama @@ -184,7 +189,7 @@ Bu iletiyi sildiniz. Güvenli oturumu sıfırla? - Bu sohbette şifreleme sorunları yaşıyorsanız bu işlem yardımcı olabilir. İletileriniz saklanacaktır. + Bu konuşmada şifreleme sorunları yaşıyorsanız bu işlem yardımcı olabilir. İletileriniz saklanacaktır. Sıfırla Eklenti ekle Kişi bilgisi seç @@ -212,6 +217,8 @@ Ses kaydedilemedi! Bu gruba ileti gönderemezsiniz çünkü artık bu gruba üye değilsiniz. Cihazınızda bu bağlantıyı işleyebilecek uygulama bulunmamaktadır. + Katılma isteğiniz grup yöneticisine gönderildi. İsteğiniz yanıtlandığı zaman haberdar edileceksiniz. + İsteği İptal Et Sesli ileti göndermek için Signal\'in mikrofonunuza erişmesine izin verin. Signal, sesli iletiler göndermek için Mikrofon iznine ihtiyaç duyar, fakat bu izin kalıcı olarak reddedilmiş. Lütfen uygulama ayarları menüsüne girip \"İzinler\" kısmını seçin, ve \"Mikrofon\"u etkinleştirin. Signal, %s ile arama yapmanız için Mikrofon ve Kamera iznine ihtiyaç duyar, fakat bu izin kalıcı olarak reddedilmiş. Lütfen uygulama ayarları menüsüne girip \"İzinler\" kısmını seçin, \"Mikrofon\" ve \"Kamera\"yı etkinleştirin. @@ -230,9 +237,9 @@ Çıkartma paketi yüklendi Yeni! Çıkartmalarınızı konuşturun İptal - Sohbet silinsin mi? + Konuşma silinsin mi? Grubu sil ve ayrıl? - Bu sohbet tüm cihazlarınızdan silinecektir. + Bu konuşma tüm cihazlarınızdan silinecektir. Bu gruptan ayrılacaksınız ve tüm cihazlarınızdan silinecek. Sil Sil ve ayrıl @@ -279,7 +286,7 @@ İletiler siliniyor… Benden sil Herkesten sil - İleti sohbetteki herkesten kalıcı olarak silinecektir. Üyeler iletiyi sildiğinizi görebileceklerdir. + İletinin aslı bulunamadı İletinin aslı artık mevcut değil İleti açılamadı @@ -293,28 +300,28 @@ \'%s\' için hiç sonuç bulunamadı - Seçilen sohbeti sil? - Seçilen sohbetleri sil? + Seçilen konuşmayı sil? + Seçilen konuşmaları sil? - Bu işlem seçilmiş sohbeti kalıcı olarak silecektir. - Bu işlem seçilmiş %1$d sohbeti kalıcı olarak silecektir. + Bu işlem seçilmiş konuşmayı kalıcı olarak silecektir. + Bu işlem seçilmiş %1$d konuşmayı kalıcı olarak silecektir. Siliniyor - Seçili sohbetler siliniyor… + Seçili konuşmalar siliniyor… - Sohbet arşivlendi - %d sohbet arşivlendi + Konuşma arşivlendi + %d konuşma arşivlendi GERİ AL - Sohbet gelen kutusuna taşındı - %d sohbet gelen kutusuna taşındı + Konuşma gelen kutusuna taşındı + %d konuşma gelen kutusuna taşındı Anahtar takas iletisi - Arşivlenmiş sohbetler (%d) + Arşivlenmiş konuşmalar (%d) Doğrulandı Siz @@ -326,7 +333,7 @@ Profil ayarlanırken hata Profil fotoğrafı Profilinizi ayarlayın - Profiliniz uçtan uca şifrelidir. Profiliniz ve profildeki değişiklikler, yeni sohbetler başlattığınızda veya kabul ettiğinizde ve yeni gruplara katıldığınızda kişileriniz tarafından görülebilir. + Profiliniz uçtan uca şifrelidir. Profiliniz ve profildeki değişiklikler, yeni konuşmalar başlattığınızda veya kabul ettiğinizde ve yeni gruplara katıldığınızda kişileriniz tarafından görülebilir. Avatar seçin Özelleştirilmiş \'%s\' kullanılıyor @@ -337,6 +344,7 @@ Fotoğraf çek Galeriden seç Fotoğrafı kaldır + Fotoğraf çekmek kameraya erişim izni gerektirir. Şimdi %dd @@ -366,6 +374,8 @@ Eksik Google Play Hizmetleri için optimize et Bu cihaz Play Hizmetlerini desteklemiyor. Signal etkin değilken ileti almasını engelleyebilecek pil iyileştirmelerini devre dışı bırakmak için dokunun. + Signal\'in bu sürümü kullanım dışı kaldı. İleti gönderip alabilmek için şimdi güncelleyin. + Şimdi güncelle Paylaşılacak kişi Çoklu ekler sadece resim ve dosyalarda destekleniyor @@ -401,6 +411,7 @@ Herhangi biri Tüm üyeler Yalnızca yöneticiler + Hiç kimse Davet gönderildi @@ -418,16 +429,32 @@ Yönetici seçin Devre dışı bırak + Tüm bağlantıları önizle Beklemedeki grup davetleri + İstekler + Davetler Davet ettiğiniz kişiler Beklemede olan davetiniz yok. Diğer grup üyelerinin davetleri Diğer grup üyelerinin bekleyen davetleri yok. Diğer grup üyeleri tarafından davet edilen kişilerin ayrıntıları gösterilmez. Davetliler katılmayı seçerse, bilgileri o zaman grupla paylaşılacaktır. Katılıncaya kadar grupta herhangi bir ileti görmezler. + Daveti iptal et + Davetleri iptal et + + Daveti iptal et + %1$d daveti iptal et + + + Davet iptal edilirken hata + Davetler iptal edilirken hata + + Beklemedeki üyelik istekleri + \"%1$s\" eklendi + \"%1$s\" reddedildi Tamam Bu kullanıcı eski bir gruba eklenemez. @@ -451,15 +478,21 @@ SMS kişisi %1$s bu gruptan çıkartılsın mı? + + %1$d üye Signal\'ın eski bir sürümünü kullandığı için eski bir grup oluşturulacaktır. Signal\'i güncelledikten sonra üyeler ile yeni bir grup oluşturabilir veya grubu oluşturmadan önce üyeleri kaldırabilirsiniz. + %1$d üye Signal\'ın eski bir sürümünü kullandığı için eski bir grup oluşturulacaktır. Signal\'i güncelledikten sonra üyeler ile yeni bir grup oluşturabilir veya grubu oluşturmadan önce üyeleri kaldırabilirsiniz. + Kaybolan iletiler Beklemedeki grup davetleri + Üyelik istekleri & davetleri Üye ekle Grubu engelle Grubun engelini kaldır Gruptan ayrıl Bildirimleri sessize al Özel bildirimler + Bahsedilmeler %1$s tarihine kadar Kapalı Açık @@ -479,10 +512,13 @@ Grubu güncelleme başarısız oldu Grubun üyesi değilsiniz Adı ve resmi düzenleyin + Eski Grup + Bu şifresiz MMS Grubudur. Özel olarak sohbet etmek ve grup adları gibi özelliklere erişmek için kişilerinizi Signal\'e davet edin. + Sessize alınan konuşmalarda sizden bahsedildiğinde bildirim gönderilsin mi? Kaybolan iletiler - Sohbet rengi + Konuşma rengi Engelle Engeli kaldır Güvenlik numarasını görüntüle @@ -524,10 +560,15 @@ Etkin Devre dışı Varsayılan + Grup bağlantısı üzerinden katılan yeni üyelerin yönetici tarafından onaylanması gereksin. + Grup bağlantısını sıfırlamak istediğinizden emin misiniz? İnsanlar artık geçerli bağlantıyı kullanarak gruba katılamazlar. + Bu kodu tarayan kişiler grubunuza katılabilir. Ayarı açıksa, yöneticilerin yeni üyeleri onaylaması gerekecektir. + Katıl Bir ağ hatası ile karşılaşıldı. + Bu gruba katılmak ve adınızı ve fotoğrafınızı üyeleriyle paylaşmak ister misiniz? Signal\'i Güncelle @@ -562,7 +603,7 @@ Hadi Signal\'e geçelim: %1$s Paylaşmak için herhangi bir uygulamanız yok gibi görünüyor. - Arkadaşlar, arkadaşlarının şifresiz sohbet etmesine izin vermez. + Arkadaşlar, arkadaşlarının şifresiz konuşmalarına izin vermez. Arkaplanda çalışıyor… @@ -736,11 +777,18 @@ Grup üyeliğini kimlerin düzenleyebileceğini \"%1$s\" olarak değiştirdiniz. %1$s grup üyeliğini kimlerin düzenleyebileceğini \"%2$s\" olarak değiştirdi. + Grup bağlantısını, yönetici onayı kapalı şekilde etkinleştirdiniz. + Grup bağlantısını, yönetici onayı ile birlikte etkinleştirdiniz. + Grup bağlantısını devre dışı bıraktınız. + Grup bağlantısını sıfırladınız. + Grup bağlantısı üzerinden gruba katıldınız. + Gruba katılmak için bir istek gönderdiniz. + Gruba katılma isteğinizi iptal ettiniz %s ile güvenlik numaranız değişti. %s ile olan güvenlik numaranızı doğrulanmış olarak işaretlediniz. @@ -752,9 +800,9 @@ Sil Engelle Engeli kaldır - %1$s kişisinin sizinle yazışmasına ve adınızla fotonuzu görmesine izin vermek istiyor musunuz? Kabul edene kadar iletilerini gördüğünüzü bilmeyecekler. - %1$s kişisinin sizinle yazışmasına ve adınızla fotonuzu görmesine izin vermek istiyor musunuz? Engeli kaldırana kadar hiçbir ileti almayacaksınız. - Bu gruba katılmak ve adınızla fotonuzu üyeleri ile paylaşmak istiyor musunuz? Kabul edene kadar mesajlarını gördüğünüzü bilmeyecekler. + %1$s kişisinin sizinle yazışmasına ve adınızla fotoğrafınızı görmesine izin vermek istiyor musunuz? Kabul edene kadar iletilerini gördüğünüzü bilmeyecekler. + %1$s kişisinin sizinle yazışmasına ve adınızla fotoğrafınızı görmesine izin vermek istiyor musunuz? Engeli kaldırana kadar hiçbir ileti almayacaksınız. + Bu gruba katılmak ve adınızla fotonuzu üyeleri ile paylaşmak istiyor musunuz? Kabul edene kadar iletilerini gördüğünüzü bilmeyecekler. Bu grubun engelini kaldırmak ve adınızla fotonuzu üyeleri ile paylaşmak istiyor musunuz? Engeli kaldırana kadar hiçbir ileti almayacaksınız. %1$s grubuna üye %1$s ve %2$s gruplarına üye @@ -794,8 +842,9 @@ Kaybolan iletiler İletileriniz kaybolmayacak. - Bu sohbette gönderilen ve alınan iletiler görüldükten %s sonra yok olur. + Bu konuşmada gönderilen ve alınan iletiler görüldükten %s sonra yok olur. + Şimdi güncelle Parola girin Signal simgesi @@ -825,6 +874,8 @@ PIN\'inizi oluşturun Yeni PIN oluştur + Uyarı + PIN\'i devre dışı bırak Bu uygulamaya puan verin Bu uygulamayı kullanmaktan hoşlanıyorsanız, lütfen bir dakikanızı ayırıp puan vererek bize yardımcı olun. @@ -922,7 +973,7 @@ Görüntü değişiklikleri kaydedilemedi \'%s\' için hiç sonuç bulunamadı. - Sohbetler + Konuşmalar Kişiler İletiler @@ -955,7 +1006,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı.
%s güvenli oturumu sıfırladı. Tekrarlanan ileti. Bu ileti Signal\'in yeni sürümünden gönderildiği için işlenemiyor. Güncelledikten sonra kişinizden bu iletiyi tekrar göndermelerini isteyebilirsiniz. - Gelen mesaj işlenirken hata oluştu. + Gelen ileti işlenirken hata oluştu. Çıkartmalar @@ -1092,7 +1143,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı.
Bu eylem bu iletiyi kalıcı olarak silecektir. %1$s --> %2$s - %2$d sohbetten %1$d yeni ileti + %2$d konuşmadan %1$d yeni ileti En yeni ileti: %1$s Kilitli ileti İçerik iletisi: %s @@ -1142,7 +1193,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı.
Kaydedildi Ara - Sohbetleri, kişileri ve iletileri ara + Konuşmaları, kişileri ve iletileri ara Geçersiz kısayol @@ -1168,7 +1219,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. Bağlı cihazdan reddedildi. Bağlı cihazda meşgul. - %1$s ile sohbetinizin güvenlik numarası değişti. Bu, birisinin iletişiminizi kesmeye çalıştığı ya da %2$s Signal\'i yeniden yüklemiş olduğu anlamına gelebilir. + %1$s ile konuşmanızın güvenlik numarası değişti. Bu, birisinin iletişiminizi kesmeye çalıştığı ya da %2$s Signal\'i yeniden yüklemiş olduğu anlamına gelebilir. Bu kişi ile güvenlik numaranızı doğrulamak isteyebilirsiniz. Yeni güvenlik numarası Kabul et @@ -1290,6 +1341,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. Güvenlik Numarası Değişiklikleri Yine de gönder + Aşağıdaki kişiler cihazd değiştirmiş veya yeniden yükleme yapmış olabilir. Gizliliğinizden emin olmak için güvenlik numaranızı doğrulayın. Görüntüle Daha önce doğrulandı @@ -1381,7 +1433,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. Şifrelenmemiş metin yedeğini içe aktar SMS yedeği ile uyumlu bir metin yedeğini İçe aktar & Geri yükle - Tüm sohbeti gör + Tüm konuşmayı gör Yükleniyor İçerik yok @@ -1464,7 +1516,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. İleti ayrıntıları Bağlı cihazlar Arkadaşlarını davet et - Arşivlenmiş sohbetler + Arşivlenmiş konuşmalar Fotoğrafı kaldır İleti istekleri @@ -1540,6 +1592,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 + 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. Parola değiştir @@ -1579,6 +1632,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. Yavaş Yardım Gelişmiş + Signal\'e Bağış Yap Gizlilik MMS Kullanıcı Aracı Manuel MMS ayarları @@ -1589,14 +1643,17 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. MMSC Parolası SMS iletim raporları Gönderdiğiniz her SMS için iletim raporu istensin - Sohbet ve içerik + Konuşmalar ve içerik Depolama - Sohbet uzunluk sınırı + Konuşma uzunluk sınırı Bağlı cihazlar Aydınlık Karanlık Görünüm Tema + PIN\'i devre dışı bırak + PIN\'i etkinleştir + PIN\'ler Signal\'de depolanan verileri şifreli olarak tutar böylelikle yalnızca siz erişebilirsiniz. Profiliniz, ayarlarınız ve kişileriniz Signal\'i yeniden yüklediğinizde geri yüklenecektir. Uygulamayı açmak için PIN\'inize ihtiyacınız yoktur. System varsayılanı Varsayılan Dil @@ -1630,10 +1687,10 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. Aramaları her zaman aktar Uygulama erişimi İletişim - Sohbetler + Konuşmalar İletiler Etkinlikler - Sohbet içi sesler + Konuşma içi sesler Göster Aramalar Zil sesi @@ -1648,6 +1705,8 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. Herkesden al Kişileriniz arasında bulunmayan ve profilinizi paylaşmadığınız kişilerden göndereninin gizli olduğu iletilerin alınmasını etkinleştir. Dahasını öğrenin + Bahsedilmeler + Sessize alınan konuşmalarda sizden bahsedildiğinde bildirim gönderilsin @@ -1687,15 +1746,17 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. Okunmadı olarak işaretle Ayarlar kısayolu - Sohbette Ara - Sohbetler + Konuşmada Ara + Sabitlendi + Konuşmalar + Yalnızca %1$d konuşmayı sabitleyebilirsiniz Kişi Fotoğrafı Arşivlendi Gelen kutusu bomboş Yok. Bitti. Kalmadı.\nÇok boş hissediyorum be! - Yeni sohbet + Yeni konuşma Kamerayı Aç Gelen kutunuza bir şeyler gelmesini sağlayın. Bir arkadaşınıza ileti göndererek başlayın. @@ -1710,7 +1771,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. Grup ayarları Gruptan ayrıl Tüm içerik - Sohbet ayarları + Konuşma ayarları Ana ekrana ekle Bekleyen üyeler @@ -1720,7 +1781,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. Alıcı listesi İletim - Sohbet + Konuşma Yayın Yeni grup @@ -1740,7 +1801,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. Signal iletilerini ve aramalarını etkinleştir İletişim deneyiminizi yükseltin. Signal\'e davet et - %1$s ile sohbetinizi bir sonraki seviyeye taşıyın. + %1$s ile konuşmanızı bir sonraki seviyeye taşıyın. Arkadaşlarını davet et! Ne kadar çok arkadaşın Signal kullanırsa, o kadar iyi hale gelir. Signal teknik sorunlar yaşıyor. Hizmeti olabildiğince çabuk düzeltmek için sıkı çalışıyoruz. @@ -1763,11 +1824,11 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. % İçgörüler İçgörüler - Signal Protokolü son %2$d gündeki iletilerinizin %%%1$d kadarını otomatik olarak korudu. Signal kullanıcıları arasındaki sohbetler her zaman uçtan uca şifrelidir. + Signal Protokolü son %2$d gündeki iletilerinizin %%%1$d kadarını otomatik olarak korudu. Signal kullanıcıları arasındaki konuşmalar her zaman uçtan uca şifrelidir. Signal\'i güçlendir Yetersiz veri İçgörü yüzdeniz son %1$d gün içerisinde gönderdiğiniz kaybolmamış veya silinmemiş iletilere göre hesaplanır. - Sohbet başlat + Konuşma başlat Kişilerinizi Signal\'e davet ederek şifresiz SMS iletilerinin sınırlarını aşan yeni özelliklerle güvenli konuşmaya başlayın. Bu istatistikler yalnızca cihazınızda oluşturulup sizin tarafınızdan görüntülenebilir. Asla hiçbir yere aktarılmaz. Şifreli iletiler @@ -1817,6 +1878,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. PIN\'i güncelle PIN\'inizi oluşturun PIN\'ler hakkında dahasını öğrenin + PIN\'i devre dışı bırak Signal PIN\'inizi girin PIN\'inizi hatırlamanıza yardımcı olmak için, arada sırada girmenizi isteyeceğiz. Zaman geçtikçe daha az soracağız. @@ -1872,9 +1934,14 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. Size daha sonra hatırlatacağız. %1$d gün sonra PIN oluşturmanız zorunlu olacaktır. Size daha sonra hatırlatacağız. %1$d gün sonra PIN\'inizi onaylamanız zorunlu olacaktır. + Signal\'e ne düşündüğünüzü söyleyin Dahasını öğrenin Kapat + Signal Araştırması + Gizliliğe inanıyoruz.

Signal sizi takip etmez veya verilerinizi toplamaz. Signal\'i herkes için geliştirmek için, kullanıcı geri bildirimlerine güveniyoruz, ve sizinkini çok isteriz.

Signal\'i nasıl kullandığınızı anlamak için bir anket yapıyoruz. Anketimiz sizi tanımlayacak herhangi bir veri toplamaz. Daha fazla geri bildirim paylaşmakla ilgileniyorsanız, iletişim bilgilerinizi sağlama seçeneğiniz vardır.

Birkaç dakikanız ve geri bildiriminiz varsa, sizden haber almaktan memnuniyet duyarız.

]]>
+ Anketi doldur Hayır teşekkürler + Ankete Surveygizmo tarafından barındırılan güvenli etki alanı surveys.signalusers.org adresinden erişilebilir Ulaştırma simgesi Yükleniyor… @@ -1894,8 +1961,8 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. Yedeği geri yükle Atla Kaydol - Sohbet yedekleri - Sohbetleri harici depolamaya yedekle + Konuşma yedekleri + Konuşmaları harici depolamaya yedekle Yedek oluştur Yedek parolasını doğrula Yedek parolanızı deneyin ve eşleştiğinden emin olun @@ -1932,6 +1999,8 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. Signal Kaydı - Android için Doğrulama Kodu Asla Bilinmiyor + Hiçkimse + Yalnızca kişileriniz telefon numaranızı Signal\'de görebilecek. Ekran kilidi Signal erişimini Android kilit ekranı ile veya parmak izi ile kilitleyin Ekran kilidi zaman aşımı @@ -2007,8 +2076,14 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. Panoya kopyalandı Yönetici + Eski ve Yeni Grup karşılaştırması + Eski Gruplar nelerdir? + Eski gruplar, yöneticiler ve daha açıklayıcı grup güncellemeleri gibi yeni grup özellikleriyle uyumlu olmayan gruplardır. + Eski bir Grubu yükseltebilir miyim? + Signal ile Paylaş Kopyala + Karekod Paylaş Panoya kopyalandı diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index c775b75f5..230b9237e 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -273,7 +273,7 @@ Đang xóa tin nhắn… Xóa cho tôi Xóa cho mọi người - Tin nhắn này sẽ được xóa vĩnh viễn cho tất cả thành viên trong cuộc trò chuyện. Các thành viên sẽ biết rằng bạn đã xóa tin nhắn. + Không tìm thấy tin nhắn ban đầu Tin nhắn ban đầu không khả dụng Không thể mở tin nhắn diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index d5891637c..f646bf790 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -82,10 +82,14 @@ 屏蔽并离开%1$s? 屏蔽%1$s? 您将不再收到来自此群组的信息或更新,同时成员将无法再将您加入此群组。 + 群组成员将无法再次将您添加到此群组。 组成员将能够再次将您添加到此组。 + 您将能与之通话和发消息,同时分享您的昵称和头像。 + 被屏蔽的用户无法向你发起通话或给你发送消息。 解除屏蔽%1$s? 取消屏蔽 屏蔽 + 屏蔽并离开 屏蔽并删除 今天 @@ -118,10 +122,14 @@ 删除 删除头像 + 删除群组头像? 升级 Signal + 此版本 Signal 已过期,升级到最新版本后才能收发消息。 更新 + 不要更新 警告 + 您的 Signal 版本已过期,目前您只能查看历史消息,只有升级后才能收发消息。 未找到网页浏览器。 找不到电子邮件应用程序。 @@ -158,6 +166,8 @@ 其他 所选联系人无效 + 未发送,点击查看详情 + 仅部分人收到消息,点击查看详情 发送失败 已收到密钥交换消息,点击处理。 %1$s 已经离开该群组。 @@ -202,7 +212,9 @@ 附件超过当前消息类型的大小限制。 相机不可用 无法录音! + 您已不是此群组成员,不能向其发送消息。 你的设备上没有处理该链接的应用。 + 您的入群申请已发送给群组管理员,您将在管理员作出决定后收到通知。 撤回申请 如需发送语音,请允许 Signal 使用麦克风。 Signal 需“麦克风”权限,来发送音频,但该权限已永久禁用。请访问应用设置菜单,选择“权限”并启用“麦克风”。 @@ -228,6 +240,9 @@ 你将离开这个组,它将从你所有的设备中删除。 删除 删除并离开 + Signal 需要访问您的麦克风来呼叫 %1$s + Signal 需要使用您的麦克风和照相机来呼叫 %1$s。 + “群组设置”现增加了更多选项 %d 条未读消息 @@ -259,6 +274,9 @@ 短信 删除中 正在删除消息… + 只对我删除 + 对所有人删除 + 未找到原始消息 原始消息已不存在 打开消息失败 @@ -266,6 +284,7 @@ 左滑任何消息可快速回复 一次性媒体文件在发送之后将自动删除 已查看该消息 + 您可在此对话中为自己做笔记。\n若您的账户有关联设备,新笔记将同步到各设备上。 设备上没有安装浏览器。 @@ -293,12 +312,14 @@ 已验证 + 部分联系人无法被置于旧版群组中。 个人资料 设置头像出错 设置个人资料出错 头像 设置个人资料 + 您的个人资料已通过端对端加密。当您发起、接受新对话以及加入新新群组时,相关用户将能看到您的个人资料及更新。 设置头像 使用定制:%s @@ -309,6 +330,7 @@ 拍照 从相册中选择… 删除图片 + 拍照需要相机权限。 刚刚 %d 分钟 @@ -338,6 +360,8 @@ 缺失 Google Play 服务时优化 该设备不支持 Google Play 服务。点击禁用系统电池优化,防止 Signal 非活动时无法获取新消息。 + 此 Signal 版本已过期,请升级到最新版本以继续收发消息。 + 现在升级 分享至 多个附件只支持图像和视频 @@ -352,12 +376,17 @@ GIF 图片 表情图片 + 添加成员 + 添加“%1$s”到“%2$s”? + 已添加“%1$s”到“%2$s”。 添加至群组 添加至群组 + 此用户不能被添加到旧版群组中。 添加 选择新的管理员。 完成 + 您退出了“%1$s”。 是否与该群组分享你的个人资料名称和头像? 对于该群组现有和未来成员,你的个人资料名称和头像是否可见? @@ -365,30 +394,65 @@ + 任何人 + 所有群成员 + 仅限管理员 + 无人 + + 已发送%d个邀请 + + “%1$s”不能直接被您邀请入群。\n\n对方已收到入群邀请,但在他同意入群前他将无法看到群消息。 了解更多 + 这些用户不能直接被你邀请入群组。\n\n他们已经收到入群邀请,但在他们同意之前无法收到来自该群组的任何消息。 离开群组? 您将无法在这个群聊里面发送和接收消息。 离开群组 选择新的管理员。 + 在你退出群组前,你必须为群组选择一名新的管理员。 + 选择管理员 关闭 + 预览任意链接 + 您可直接从您发送的链接网站中生成预览。 + 没有预览链接可用 + 此群组链接未激活 + %1$s · %2$s + + %1$d位成员 + + 待处理的入群邀请 + 入群申请 + 邀请 你邀请的人 + 您没有待处理的邀请。 其他群组成员的邀请 + 没有来自其它群成员的待处理邀请。 未显示其他群组成员邀请的人员详情。当受邀者选择加入时,他们的信息将分享至群组。在加入之前,他们不会看到任何消息。 撤回邀请 撤回邀请 + + 撤回%1$d条邀请 + 撤回邀请时出现错误 + 待处理入群申请 + 没有入群申请。 + 此表中的人想通过群组链接入群。 已添加 “%1$s” + 已拒绝“%1$s” 完成 + 此用户不能被添加到旧版群组中。 + + 添%3$d位成员到“%2$s”? + 添加 命名该群组 @@ -396,23 +460,67 @@ 创建 成员 群名称(必填) + 必须填写此项。 + 群组至少得包含两位成员。 + 群组创建失败。 + 稍候再试。 + 您所选择的联系人并不支持 Signal 群组,所以此组将以彩信形式存在。 删除 + 短信联系人 + 将 %1$s 移出此群组? + + 有%d位成员无法使用新版群组,所以将创建旧版群组。 + + 将创建旧版群组,因为“%1$s”还在使用旧版 Signal。您可以在他们都升级到新版 Signal 后创建新版群组,或将他们排除后再建群。 + + 将创建旧版群组因为%1$d位群成员还在使用旧版 Signal。您可以在他们都升级到新版 Signal 后创建新版群组,或将他们排除后再建群。 + 阅后即焚 + 待处理的入群邀请 + 入群申请和邀请 添加成员 + 编辑群组信息 + 谁能添加新成员? + 谁能编辑群组信息? + 群组链接 + 屏蔽群组 + 取消屏蔽群组 离开群组 静音通知 自定义通知 提及我 + 直到 %1$s 查看全体群成员 查看全部 + + 已邀请%d位 + + + 已添加%d位成员。 + + 您没有此操作的权限 + 您添加的部分用户无法使用新版群组,这些用户需要升级 Signal。 + 无法更新群组 + 您不是该群组成员 + 无法更新群组,请稍候再试 + 由于网络错误无法更新群组,请稍候再试 + 编辑昵称和头像 旧版群组 + 这是个旧版群组,管理员等功能支持新版群组。 + 这是个未加密的彩信群组。要使用加密聊天和群组名称等功能,请邀请您的联系人加入 Signal。 + 现在邀请 + 提到我时通知我 静音群聊中被别人@时,是否接收提醒? + 默认(通知我) + 默认(不通知我) + 总是通知我 + 总是不通知我 添加至系统联系人 此人是你的联系人 @@ -423,18 +531,28 @@ 查看安全码 静音通知 自定义通知 + 直到 %1$s 添加至群组 + 查看所有群组 查看全部 无共同群组 %d 个共同群组 + 编辑昵称和头像 消息 + 语音通话 + 未加密的语音通话 + 视频通话 + + %1$s 邀请了%2$d位用户 + 自定义通知 消息 + 使用自定义通知 通知声音 振动 通话设置 @@ -443,23 +561,54 @@ 已禁用 默认 + 可分享的群组链接 + 管理和分享 + 群组链接 分享 + 重置链接 + 入群申请 + 审核新成员 已启用 已禁用 默认 + 群组链接已重置 通过群组链接的新成员需要经过管理员同意才能入群。 确定要重置群组链接吗?重置后他人将无法通过原来的链接加入群组。 + 二维码 扫描此码的人即可加入您的群组。如果您激活了管理员审核,那么入群者还需要管理员同意才能入群。 + 分享二维码 + 是否撤销您发给 %1$s 的入群邀请? + + 是否撤销 %1$s 发出的 %2$d 条入群邀请? + + 您已在群组中 加入 + 申请入群 + 无法加入群组,请稍候再试 网络出错。 + 此群组链接未激活 + 无法获取群组信息,请稍候再试 您要加入此群组并向其成员分享您的名字和头像吗? + 您需要经过此群管理员同意才能加入群组。当您申请入群时,群组成员将能看到您的您的昵称和头像。 + + 群组 · %1$d位成员 + + 即将推出群组链接功能 + 升级 Signal 以使用群组链接 + Signal 暂时不支持通过链接入群,此功能会在近期的版本更新中引入。 + 您当前版本的 Signal 不支持群组链接。请升级到最新版来通过链接入群。 升级 Signal + 您有至少一台关联设备不支持群组链接,请升级其 Signal 版本来加入此群。 + 群组链接无效 + 将“%1$s”添加到群组? + 拒绝“%1$s”的入群申请? 添加 + 拒绝 群组头像 头像 @@ -584,6 +733,7 @@ 收到来自旧版本 Signal 的加密消息,该版本已不再支持。请提醒发送人升级至最新版本并重发该消息。 您已经退出该群组。 您已更新该群组 + 群组已更新。 您曾呼叫 联系人曾呼叫 未接来电 @@ -596,38 +746,116 @@ %1$s 已禁用阅后即焚。 设置阅后即焚消失时间为 %1$s。 %1$s 设置阅后即焚消失时间为 %2$s。 + 阅后即焚消息的销毁时间被设置为 %1$s。 + %1$s 更改其昵称为 %2$s。 %1$s 的个人资料名称已从 %2$s 更改为 %3$s。 + %1$s 更改了其资料。 您创建了群组 群组更新完成。 + 已添加 %1$s。 + %1$s 添加了 %2$s。 + %1$s 已将您加入群组。 你已加入该群组。 %1$s 加入群组。 + 您移除了 %1$s。 + %1$s 移除了 %2$s。 + %1$s 已将您移出群组。 + 您退出了群组。 + %1$s 退出了群组。 + 您已不在群组中。 + %1$s 已不在群组中。 + 您已将 %1$s 设为管理员。 + %1$s 已将 %2$s 设为管理员。 + %1$s 已将您设为管理员。 + 您已撤销 %1$s 的管理员权限。 + %1$s 撤销了您的管理员权限。 + %1$s 撤销了 %2$s 的管理员权限。 + %1$s 已成为管理员。 你被设置为管理员。 + %1$s 已不是管理员。 + 您已不是管理员。 + 您邀请了 %1$s 入群。 + %1$s 了您入群。 + + %1$s 邀请了%2$d位用户入群。 + 你被邀请加入此群 + + %1$d 位用户收到了入群邀请。 + + + 您撤销了%1$d条入群邀请。 + + + %1$s 撤销了%2$d条入群邀请。 + + 某人回绝了入群邀请。 + 您回绝了入群邀请。 + %1$s 撤销了对您的入群邀请。 + 管理员撤销了对您的入群邀请。 + + %1$d条入群邀请被撤销。 + + 您接受了入群邀请。 + %1$s 接受了入群邀请。 + 您添加了受邀用户 %1$s。 + %1$s 添加了受邀用户 %2$s。 + 您更改了群名称为%1$s”。 + %1$s 更改了群名称为 “%2$s”。 + 群组名称更改为“%1$s”。 + 您更改了群组头像。 + %1$s 更改了群组头像。 + 群组头像已更改。 + 你更改了能编辑群资料的权限为“%1$s”。 + %1$s 更改了能编辑群资料的权限为“%2$s”。 + 能编辑群资料的权限已设为“%1$s”。 + 你更改了能编辑群成员的权限为“%1$s”。 + %1$s 更改了能编辑群成员的权限为“%2$s”。 + 能编辑群成员的权限已设为“%1$s”。 您打开了无需管理员认证的群组链接。 你已打开了启用管理员认证的群组链接。 您关闭了群组链接。 + %1$s 打开了无需管理员审核的群组链接。 + %1$s 启用了需要管理员审核的群组链接。 + %1$s 关闭了群组链接。 + 无需管理员审核的群组链接已启用。 + 需要管理员审核的群组链接已启用。 + 群组链接已关闭。 您重置了群组链接。 + %1$s 重置了群组链接。 + 群组链接已重置。 您通过群组链接加入了群组。 + %1$s 已通过群组链接入群组。 您发送了入群请求。 + %1$s 通过群组链接申请入群。 + %1$s 同意了您的入群申请。 + %1$s 同意了 %2$s 的入群申请。 + 您同意了 %1$s 的入群申请。 + 您的入群申请已通过。 + %1$s 的入群申请已通过。 + 您的入群申请已被管理员拒绝。 + %1$s 拒绝了 %2$s 的入群申请。 + %1$s 的入群申请未通过。 您撤回了入群请求。 + %1$s 撤回了其入群申请。 您与 %s 的安全码已更改。 已将您与 %s 的安全码标记为已验证 @@ -639,6 +867,10 @@ 删除 屏蔽 取消屏蔽 + 是否允许 %1$s 给您发消息并查看您的昵称头像?您同意前对方无法知晓您是否查看了其消息。 + 是否允许 %1$s 给您发消息并查看您的昵称头像?取消屏蔽前您将不会收到其发来的消息。 + 是否加入此群组并与之分享您的名字和头像?接受邀请前群成员无法知道您是否阅读了他们发来的消息。 + 是否解除对该群组的屏蔽并与之分享您的名字和头像?解除屏蔽前您将不会收到其发来的消息。 %1$s 的成员 %1$s 和 %2$s 的成员 %1$s、%2$s 和 %3$s 的成员 @@ -648,6 +880,9 @@ %1$d成员(+%2$d 获邀请) + + 更多%d个群组 + 密码不匹配! 错误的旧密码! @@ -677,6 +912,11 @@ 消息不会消失。 该对话接收和发送的消息将于 %s 后消失。 + 现在升级 + 此 Signal 版本将在今天过期,请升级到最新版本。 + + 此版本 Signal 将在%d天后过期,请升级到最新版本。 + 输入密码 Signal 图标 @@ -692,15 +932,24 @@ 当前安装的 Google Play 服务版本无法正常运行。请重新安装 Google Play 服务并重试。 PIN 错误 + 跳过 PIN 码设置? + 需要协助? + PIN 码是您创建的%1$d位及以上的密码,可以是纯数字或数字字母混合。如果忘了 PIN 码,您可以注册一个新账户,但您会丢失所有保存过的设置,比如您的个人介绍。 如果忘记你的 PIN,可再新建一个。你可以注册并使用你的账号,但某些已保存设置,如个人资料信息,将丢失。 创建新 PIN 联系支持团队 取消 跳过 + + 您还剩%1$d次机会。若尝试次数达到上限,您可以创建新的 PIN 码。您可以注册和使用您的账户,但会丢失所有保存过的设置,比如您的个人介绍。 + + Signal 注册 - 需要安卓 PIN 码相关帮助 输入字母数字 PIN 输入数字 PIN 创建 PIN + 你的 PIN 码错误次数达到上限,但你还是可以通过创建新的 PIN 码以进入你的 Signal 账户。出于对你的隐私和安全考虑,你的账户会在不保存任何个人资料或设置下被还原。 + 新建 PIN 警告 如果禁用 PIN,重新注册 Signal 时全部数据将丢失,除非使用手动备份并恢复。PIN 禁用时,无法启用注册锁定。 @@ -713,6 +962,7 @@ 稍后 糟糕, 你的设备上未安装 Google Play 商店。 + 全部 · %1$d +%1$d @@ -727,6 +977,9 @@ 未命名群组 + 正在接听… + 正在挂断… + 拨号中… 响铃中… 占线 已接通 @@ -737,8 +990,21 @@ 知道了 点击这里以启用视频 + Signal 需要访问您的麦克风来呼叫 %1$s + Signal 呼叫 %1$s + 呼叫中… + 群组通话 + Signal 语音通话… + Signal 视频通话… + 发起通话 + 群组通话 + 查看参与者 + 您的视频已关闭 + + 此通话 · %1$d位用户 + 选择国家 必须指定 @@ -761,6 +1027,7 @@ 更多信息 简略信息 为了联系朋友,交换消息与进行加密呼叫,Signal 需要访问联系人和媒体。 + 您尝试注册此号码次数过多,请稍候再试。 无法连接到 Signal 服务,请检查网络连接并重试。 为了便于验证你的号码,在允许 Signal 读取短信时,Signal 可自动检测验证码。 @@ -780,7 +1047,11 @@ 国家代码 呼叫 + 启用注册锁? + 关闭注册锁? 再次注册 Signal 时,如果忘记 Signal PIN,将在 7 天内无法使用你的帐户。 + 启用 + 关闭 查看图片 查看视频 @@ -808,6 +1079,7 @@ 几天后提示。 一周后提示。 几周后提示。 + 我们会在一个月后再次提醒您。 图片 表情 @@ -860,6 +1132,8 @@ 设备信息: Android版本: Signal版本: + Signal 软件包: + 注册锁定: 语言环境: 群组已更新 @@ -886,6 +1160,7 @@ 无法处理消息 通信请求 %1$s把你也加进来了 + %1$s 邀请您入群 图片 GIF 语音消息 @@ -916,9 +1191,11 @@ 网络出错。 该用户名已有人使用。 该用户名可用。 + 用户名仅能包含字母、数字和下划线。 用户名不能以数字开头。 用户名无效。 用户名所含字符数量必须在 %1$d 与 %2$d 之间。 + Signal 上的用户名是可选功能。如果您选择创建用户名,其他 Signal 用户无需知道您的电话号码,即可通过此用户名与您取得联系。 联系人正在使用旧版 Signal。在验证安全码之前,请提示他们升级。 联系人正在使用较新版本的 Signal,其二维码格式不兼容。请升级并比较。 @@ -958,6 +1235,7 @@ 删除消息? 这将永久删除该消息。 %1$s 至 %2$s + 媒体已失效。 %2$d 个对话中有 %1$d 条新消息 最新消息来自:%1$s @@ -968,6 +1246,7 @@ 发送消息出错。 全部已读 已读 + 关闭这些通知 媒体消息 表情 一次性图片 @@ -988,6 +1267,7 @@ 对你的一次性视频回应 %1$s。 对你的表情回应 %1$s。 消息已删除。 + 关闭联系人加入 Signal 的通知吗?您可以在 Signal > 设置 > 通知 中再次启用。 默认 通话 @@ -1030,6 +1310,9 @@ 为了接听 %s 的来电,请允许 Signal 访问麦克风。 Signal 需“麦克风”和“相机”权限,来进行通话,但该权限已永久禁用。请访问应用设置菜单,选择“权限”并启用“麦克风”和“相机”。 + 已在在其它设备上接听。 + 已在其它设备上拒接。 + 在其它设备上忙碌未接。 您与 %1$s 对话的安全码已更改。可能有人试图截获你的通信,或者只是 %2$s 重装了 Signal。 您可通知该联系人验证安全码。 @@ -1041,6 +1324,8 @@ 接听 关视频接 + 音频输出 + 电话耳机 扬声器 蓝牙 @@ -1093,12 +1378,17 @@ 未找到该用户名 “%1$s”不是 Signal 用户,请检查用户名并重试。 好的 + 此群已满 + 您不需要将自己添加到群组中 没有已屏蔽联系人 为了显示联系人,Signal 需要其访问权限。 显示联系人 + + %1$d位成员 + 发送 Signal 消息 未加密短信 @@ -1113,6 +1403,7 @@ 录音并分享附件 锁定音频附件录音状态 启用 Signal 短信功能 + 无法发送消息,请检查您的网络后再试。 滑动取消 取消 @@ -1144,10 +1435,15 @@ 滚动到底部 安全码更变 + 仍要发送 + 仍要呼叫 + 以下用户可能重新安装了 Signal 或更换了设备,请重新与之验证安全码以保护您的隐私。 查看 + 已验证 正在加载国家… 搜索 + 没有相应国家 扫描设备上显示的二维码进行关联 @@ -1254,6 +1550,7 @@ 用户名 创建用户名 + 编辑群昵称和头像 群组名称 分享媒体 @@ -1289,6 +1586,12 @@ 消失 通道 + 处理中 + 发至 + 收自 + 送达 + 已读 + 未发 发送失败 新的安全码 @@ -1329,10 +1632,17 @@ 请尽可能描述一下,以帮助我们理解该问题。 找不到电子邮件应用程序。 + 此消息 + 最近使用 + 笑脸和情感 + 自然 + 食物 活动 + 地点 物品 符号 旗帜 + 颜文字 导入 使用默认 @@ -1371,6 +1681,7 @@ 使用 Signal 接收全部彩信。 按回车发送 按回车键发送短信 + 生成链接预览 直接从您发送的链接网站中生成预览。 选择联系人 从联系人列表选择联系人条目。 @@ -1463,7 +1774,14 @@ 文件 音频 查看存储 + 删除更早消息? 清除消息记录? + 这将从您的设备中,永久删除所有早于%1$s的聊天记录和媒体。 + 这将删减对话使之仅保留最近%1$s条消息。 + 这将从您的设备中,永久删除所有聊天记录和媒体。 + 您要删除所有聊天记录吗? + 将清空所有聊天记录,此操作无法撤销。 + 马上删除所有 永久 1 年 6 个月 @@ -1475,6 +1793,7 @@ 禁用 Signal 内置表情 通过 Signal 服务器中转全部通话,避免向联系人显示 IP 地址。启用该选项会降低通话质量。 总是转发通话 + 谁能… 应用访问 通信 聊天 @@ -1496,6 +1815,10 @@ 对于非联系人和未与其分享个人资料的其他人,启用传入消息的加密发送人功能。 了解更多 提及我 + 通知我 + 静音群聊中被他人提及时,是否接收提醒? + 设置用户名 + 自定义选项 @@ -1527,15 +1850,20 @@ 邀请 删除已选 + 已选标记 + 已选取消标记 选择全部 存档已选择 取消存档已选择项目 取消存档已选择项目 标记为已读 + 标记为未读 设置快捷方式 搜索 + 已标记 聊天 + 您最多只能标记%1$d个对话。 联系人图片 已存档 @@ -1559,6 +1887,7 @@ 全部媒体 对话设置 添加到主屏幕 + 待处理成员 展开弹窗 @@ -1643,6 +1972,7 @@ 新建 PIN 只有该设备已注册,你可以更改你的 PIN。 创建 PIN 密码 + PIN 用于加密 Signal 存储的信息,只有您可以访问。借此可在您重装后恢复个人资料、设置和联系人。打开 Signal 无需使用 PIN。 选择更安全 PIN PIN 不匹配,请重试。 @@ -1650,9 +1980,11 @@ PIN 创建失败 PIN 没有保存。我们将稍后提示您创建 PIN。 PIN 已创建。 + 重新入您的 PIN 正在创建 PIN… 新推出 PIN 密码功能 + PIN 用于加密 Signal 存储的信息,只有您可以访问。借此可在您重装后恢复个人资料、设置和联系人。打开 Signal 无需使用 PIN。 了解更多 注册锁定 = PIN 注册锁定现在改名为 PIN,功能更强大,马上更新。 @@ -1701,17 +2033,25 @@ 好的 + %1$s 将收到您的消息请求,对发接受邀请后您即可向其发起通话。 创建你的密码 + Signal 储存的信息使用你的 PIN 码来加密。 创建密码 新推出 PIN 密码功能 更新 PIN 稍后您会被提示。在%1$d天内会强制要求创建PIN码。 稍后您会被提示。在%1$d天内会强制要求确认你的PIN码。 + 发表您的 Signal 的感想 + 您的意见将成为 Signal 称霸即时通信界的基础,我们期待您的反馈! 了解更多 清除 + Signal 搜索 + 我们尊重隐私。

Signal 不会跟踪或搜集您的数据。我们需要用户反馈来改进 Signal ,你的宝贵意见不可或缺。

我们正在做一份关于您使用 Signal 的调研。此调研不会搜集任何泄漏您身份的信息。若有兴趣分享更多反馈,您可以选择性提供联系方式。

如果您能抽出几分钟完成调研,我们将翘首以待。

]]>
+ 参与调研 不,谢谢。 + 此调研由 Surveygizmo 于在安全域 surveys.signalusers.org 上运行 传输图标 正在加载… @@ -1769,7 +2109,14 @@ Signal 注册 - 安卓验证码 永不 未知 + 查看我的电话号码 + 通过电话号码找到我 + 所有人 + 我的联系人 没有人 + 您的电话号码将对所有对话者和群组成员可见。 + 电话簿里存有您电话号码的用户会在 Signal 联系人列表里看到您。其它知晓您电话号码的用户将能在搜索界面找到您。 + 只有您的联系人能看到您 Signal 帐号的电话号码。 锁屏 使用 Android 锁屏或者指纹锁定 Signal 不活跃锁屏逾时 @@ -1780,9 +2127,13 @@ PIN 用于加密 Signal 存储的信息,这样只有你可以访问。重装 Signal 之后,可恢复你的个人资料、设置和联系人。 再次使用电话号码注册 Signal 时,要求输入 PIN,这样提供了更多安全性。 PIN 无法恢复,提醒有助于记住它。提醒频率将逐渐降低。 + 关闭 确认 PIN 请验证你的 Signal 安全码 + 请确保你能牢记或安全保存好你的 PIN 码,因为它不能被找回。如果你忘记了 PIN 码,你可能会在重新注册 Signal 账号时丢失一些数据。 PIN 错误,请重试。 + 无法启用注册锁。 + 无法禁用注册锁。 注册锁定 PIN 密码不是您刚刚收到的短信验证码。请输入之前在应用中设置的 PIN。 注册锁定 PIN @@ -1800,6 +2151,7 @@ 注册锁定 PIN 错误 尝试次数过多 注册锁定 PIN 输入错误过多,请一天后重试。 + 您的尝试次数过多,请稍候再试。 连接至服务时出错 哦不! 如果没有锁定注册 PIN,可在上次使用 Signal 后 7 天,再次使用该号码注册。目前还需等待 %d 天。 @@ -1809,6 +2161,7 @@ 忘记 PIN 密码。 忘记 PIN? 注册锁定可防止他人未经授权使用你的号码注册。可随时在 Signal 隐私设置里禁用该功能。 + 注册锁定 启用 注册锁定 PIN 必须不少于 %d 位数字。 两次输入的 PIN 不匹配。 @@ -1830,20 +2183,34 @@ 添加至群组 添加至另一群组 查看安全码 + 设为群组管理员 + 取消其管理员身份 + 移出群组 消息 + 语音通话 + 未加密的语音通话 + 视频通话 + 取消 %1$s 的管理员身份? + “%1$s” 将能编辑此群组及其成员。 + 将 %1$s 移出此群组? 删除 已复制到剪切板 管理员 + 同意 + 拒绝 新旧版群组对比 什么是旧版群组? 旧版群组不兼容新版群组的功能,如管理员和更完善群组更新通知。 我能升级旧版群组吗? + 旧版群组暂时无法升级为新版群组,但若群成员都已升级到最新版 Signal,您可以创建一个包含相同成员的新版群组。 + Signal 将在未来提供升级旧版群组的渠道。 通过 Signal 分享 复制 QR 代码 分享 已复制到剪切板 + 此链接尚未激活 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 32cf26232..a6ec8b1bf 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -276,7 +276,7 @@ 刪除訊息中… 為我刪除 為大家刪除 - 該訊息將被永久刪除。 成員將能夠看到你刪除了一則訊息。 + 無法找到原始訊息 原始訊息已不存在 開啟訊息失敗 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9097d39ec..6504891fc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -327,7 +327,7 @@ Deleting messages… Delete for me Delete for everyone - This message will be permanently deleted for everyone in the conversation. Members will be able to see that you deleted a message. + This message will be deleted for everyone in the conversation if they’re on a recent version of Signal. They will be able to see that you deleted a message. Original message not found Original message no longer available Failed to open message @@ -618,6 +618,10 @@ %d members added.
+ Only admins can enable or disable the sharable group link. + Only admins can enable or disable the option to approve new members. + Only admins can reset the sharable group link. + You don\'t have the rights to do this Someone you added does not support new groups and needs to update Signal Failed to update the group @@ -1013,6 +1017,12 @@ The group link has been turned on with admin approval off. The group link has been turned on with admin approval on. The group link has been turned off. + You turned off admin approval for the group link. + %1$s turned off admin approval for the group link. + The admin approval for the group link has been turned off. + You turned on admin approval for the group link. + %1$s turned on admin approval for the group link. + The admin approval for the group link has been turned on. You reset the group link. @@ -1057,6 +1067,7 @@ Let %1$s message you and share your name and photo with them? They won\'t know you\'ve seen their message until you accept. Let %1$s message you and share your name and photo with them? You won\'t receive any messages until you unblock them. Join this group and share your name and photo with its members? They won\'t know you\'ve seen their messages until you accept. + Join this group? They won’t know you’ve seen their messages until you accept. Unblock this group and share your name and photo with its members? You won\'t receive any messages until you unblock them. Member of %1$s Member of %1$s and %2$s diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 0cf0466bc..65c8ca111 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -132,6 +132,11 @@ @anim/slide_to_top + + + + + +