From 8fc9893ecd4db190dc7629e5dda250eb5148b71b Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Wed, 21 Feb 2024 13:04:42 -0500 Subject: [PATCH 01/18] Improve logging around retries archiving sessions. --- .../messages/MessageContentProcessor.kt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.kt index 39a5fbf485..3a946beda8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.kt @@ -657,12 +657,16 @@ open class MessageContentProcessor(private val context: Context) { return } - if (decryptionErrorMessage.ratchetKey.isPresent && - ratchetKeyMatches(requester, metadata.sourceDeviceId, decryptionErrorMessage.ratchetKey.get()) - ) { - warn(envelope.timestamp!!, "[RetryReceipt-I] Ratchet key matches. Archiving the session.") - ApplicationDependencies.getProtocolStore().aci().sessions().archiveSession(requester.requireServiceId(), metadata.sourceDeviceId) - archivedSession = true + if (decryptionErrorMessage.ratchetKey.isPresent) { + if (ratchetKeyMatches(requester, metadata.sourceDeviceId, decryptionErrorMessage.ratchetKey.get())) { + warn(envelope.timestamp!!, "[RetryReceipt-I] Ratchet key matches. Archiving the session.") + ApplicationDependencies.getProtocolStore().aci().sessions().archiveSession(requester.requireServiceId(), metadata.sourceDeviceId) + archivedSession = true + } else { + log(envelope.timestamp!!, "[RetryReceipt-I] Ratchet key does not match. Leaving the session as-is.") + } + } else { + warn(envelope.timestamp!!, "[RetryReceipt-I] Missing ratchet key! Can't archive session.") } if (messageLogEntry != null) { From 755fafb0b6b4185f75a827c2090b4651e309b51b Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Wed, 21 Feb 2024 13:08:43 -0500 Subject: [PATCH 02/18] Always use US locale when logging rounded numbers. --- .../src/main/java/org/signal/core/util/DoubleExtensions.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core-util-jvm/src/main/java/org/signal/core/util/DoubleExtensions.kt b/core-util-jvm/src/main/java/org/signal/core/util/DoubleExtensions.kt index 21823a500e..50f2ff831e 100644 --- a/core-util-jvm/src/main/java/org/signal/core/util/DoubleExtensions.kt +++ b/core-util-jvm/src/main/java/org/signal/core/util/DoubleExtensions.kt @@ -5,6 +5,8 @@ package org.signal.core.util +import java.util.Locale + /** * Rounds a number to the specified number of places. e.g. * @@ -12,5 +14,5 @@ package org.signal.core.util * 1.123456f.roundedString(5) = 1.12346 */ fun Double.roundedString(places: Int): String { - return String.format("%.${places}f", this) + return String.format(Locale.US, "%.${places}f", this) } From d5cf8d36b319adcf1b36c268c4e8ccc7df9fc09f Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Wed, 21 Feb 2024 14:06:34 -0500 Subject: [PATCH 03/18] Fix username recovery UX bugs. --- .../components/settings/app/AppSettingsActivity.kt | 2 -- .../profiles/manage/UsernameEditFragment.java | 2 +- .../securesms/profiles/manage/UsernameEditViewModel.kt | 10 ++++++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsActivity.kt index 99fbb98f42..30fcf18ba6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsActivity.kt @@ -120,8 +120,6 @@ class AppSettingsActivity : DSLSettingsActivity(), DonationPaymentComponent { override fun onWillFinish() { if (wasConfigurationUpdated) { setResult(MainActivity.RESULT_CONFIG_CHANGED) - } else { - setResult(RESULT_OK) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditFragment.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditFragment.java index 5c82d09622..da9f3e5195 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditFragment.java @@ -141,7 +141,7 @@ public void onDestroyView() { } private void promptOrSubmitUsername() { - if (args.getMode() == UsernameEditMode.RECOVERY) { + if (viewModel.isSameUsernameRecovery()) { new MaterialAlertDialogBuilder(requireContext()) .setMessage(R.string.UsernameEditFragment_recovery_dialog_confirmation) .setPositiveButton(android.R.string.ok, ((dialog, which) -> { diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.kt index dbe059e317..8806517e9a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.kt @@ -35,8 +35,6 @@ import java.util.concurrent.TimeUnit * Usernames are made up of two discrete components, a nickname and a discriminator. They are formatted thusly: * * [nickname].[discriminator] - * - * The nickname is user-controlled, whereas the discriminator is controlled by the server. */ internal class UsernameEditViewModel private constructor(private val mode: UsernameEditMode) : ViewModel() { private val events: PublishSubject = PublishSubject.create() @@ -71,6 +69,7 @@ internal class UsernameEditViewModel private constructor(private val mode: Usern if (mode == UsernameEditMode.RECOVERY) { onNicknameUpdated(SignalStore.account().username?.split(Usernames.DELIMITER)?.first() ?: "") + onDiscriminatorUpdated(SignalStore.account().username?.split(Usernames.DELIMITER)?.last() ?: "") } } @@ -129,6 +128,13 @@ internal class UsernameEditViewModel private constructor(private val mode: Usern events.onNext(Event.SKIPPED) } + fun isSameUsernameRecovery(): Boolean { + val usernameState = uiState.state.usernameState + return mode == UsernameEditMode.RECOVERY && + usernameState is UsernameState.Reserved && + usernameState.requireUsername().username.lowercase() == SignalStore.account().username?.lowercase() + } + /** * @param userConfirmedResetOk True if the user is submitting this after confirming that they're ok with resetting their username via [Event.NEEDS_CONFIRM_RESET]. */ From 16faf41a84bd7a1a20346c6b0d9039da0df515fd Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Wed, 21 Feb 2024 15:29:17 -0500 Subject: [PATCH 04/18] Fix profile name not updating correctly. --- .../org/thoughtcrime/securesms/database/RecipientTable.kt | 3 ++- .../org/thoughtcrime/securesms/jobs/RetrieveProfileJob.kt | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt index 9158a5f5e8..a9cb71615c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt @@ -4112,7 +4112,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da PROFILE_FAMILY_NAME to null, PROFILE_JOINED_NAME to null, LAST_PROFILE_FETCH to 0, - PROFILE_AVATAR to null + PROFILE_AVATAR to null, + PROFILE_SHARING to 0 ) .run { if (recipientId == null) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.kt index 7e25d10e93..8de40d2c0b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.kt @@ -384,9 +384,12 @@ class RetrieveProfileJob private constructor(parameters: Parameters, private val } if (writeChangeEvent || localDisplayName.isEmpty()) { + ApplicationDependencies.getDatabaseObserver().notifyConversationListListeners() val threadId = SignalDatabase.threads.getThreadIdFor(recipient.id) if (threadId != null) { - ApplicationDependencies.getMessageNotifier().updateNotification(context, forConversation(threadId)) + SignalDatabase.runPostSuccessfulTransaction { + ApplicationDependencies.getMessageNotifier().updateNotification(context, forConversation(threadId)) + } } } From 418ad51e77813b397a87edb84f2b582d27f16bee Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Wed, 21 Feb 2024 16:28:48 -0500 Subject: [PATCH 05/18] Fix share your username popup icon tint. --- app/src/main/res/layout/edit_profile_fragment.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/layout/edit_profile_fragment.xml b/app/src/main/res/layout/edit_profile_fragment.xml index 567dde5385..d4dc59887c 100644 --- a/app/src/main/res/layout/edit_profile_fragment.xml +++ b/app/src/main/res/layout/edit_profile_fragment.xml @@ -401,6 +401,7 @@ android:layout_marginStart="16dp" android:layout_marginTop="30dp" android:src="@drawable/symbol_share_android_24" + app:tint="@color/signal_colorOnPrimaryContainer" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> From 64fc0209f473cb05af764362ce4eb2384e3e2d04 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Thu, 22 Feb 2024 12:44:19 -0500 Subject: [PATCH 06/18] Remove unused endpoint. --- .../signalservice/api/SignalServiceAccountManager.java | 10 ---------- .../signalservice/internal/push/PushServiceSocket.java | 9 --------- 2 files changed, 19 deletions(-) diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java index 15fda4cc94..3d66981af2 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java @@ -350,16 +350,6 @@ public OneTimePreKeyCounts getPreKeyCounts(ServiceIdType serviceIdType) throws I return this.pushServiceSocket.getAvailablePreKeys(serviceIdType); } - /** - * Set the client's signed prekey. - * - * @param signedPreKey The client's new signed prekey. - * @throws IOException - */ - public void setSignedPreKey(ServiceIdType serviceIdType, SignedPreKeyRecord signedPreKey) throws IOException { - this.pushServiceSocket.setCurrentSignedPreKey(serviceIdType, signedPreKey); - } - /** * @return True if the identifier corresponds to a registered user, otherwise false. */ diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java index 2d4f33f01b..f5f0545427 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java @@ -232,7 +232,6 @@ public class PushServiceSocket { private static final String PREKEY_METADATA_PATH = "/v2/keys?identity=%s"; private static final String PREKEY_PATH = "/v2/keys?identity=%s"; private static final String PREKEY_DEVICE_PATH = "/v2/keys/%s/%s?pq=true"; - private static final String SIGNED_PREKEY_PATH = "/v2/keys/signed?identity=%s"; private static final String PROVISIONING_CODE_PATH = "/v1/devices/provisioning/code"; private static final String PROVISIONING_MESSAGE_PATH = "/v1/provisioning/%s"; @@ -880,14 +879,6 @@ private List getPreKeysBySpecifier(SignalServiceAddress destinatio } } - public void setCurrentSignedPreKey(ServiceIdType serviceIdType, SignedPreKeyRecord signedPreKey) throws IOException { - String path = String.format(SIGNED_PREKEY_PATH, serviceIdType.queryParam()); - SignedPreKeyEntity signedPreKeyEntity = new SignedPreKeyEntity(signedPreKey.getId(), - signedPreKey.getKeyPair().getPublicKey(), - signedPreKey.getSignature()); - makeServiceRequest(path, "PUT", JsonUtil.toJson(signedPreKeyEntity)); - } - public void retrieveAttachment(int cdnNumber, SignalServiceAttachmentRemoteId cdnPath, File destination, long maxSizeBytes, ProgressListener listener) throws IOException, MissingConfigurationException { From 0a3de42729e0f84cb5c0f2428d80252799f8b681 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Thu, 22 Feb 2024 12:48:35 -0500 Subject: [PATCH 07/18] Fix username QR image generation for multiline usernames. --- .../main/UsernameLinkSettingsViewModel.kt | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/UsernameLinkSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/UsernameLinkSettingsViewModel.kt index b902f04d56..8483195714 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/UsernameLinkSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/UsernameLinkSettingsViewModel.kt @@ -304,7 +304,7 @@ class UsernameLinkSettingsViewModel : ViewModel() { } // Draw the username - val usernamePaint = Paint().apply { + val usernamePaint = TextPaint().apply { color = state.qrCodeColorScheme.textColor.toArgb() textSize = usernameTextSize typeface = if (Build.VERSION.SDK_INT < 26) { @@ -316,10 +316,18 @@ class UsernameLinkSettingsViewModel : ViewModel() { .build() } } - val usernameBounds = Rect() - usernamePaint.getTextBounds(state.username, 0, state.username.length, usernameBounds) - androidCanvas.drawText(state.username, (width / 2f) - (usernameBounds.width() / 2f), usernameVerticalPad + usernameBounds.height(), usernamePaint) + val usernameMaxWidth = qrBorderWidth - borderSizeX * 2f + val usernameLayout = StaticLayout(state.username, usernamePaint, usernameMaxWidth.toInt(), Layout.Alignment.ALIGN_CENTER, 1f, 0f, true) + val usernameVerticalOffset = when (usernameLayout.lineCount) { + 1 -> 0f + 2 -> usernameTextSize / 2f + else -> usernameTextSize + } + + androidCanvas.withTranslation(x = backgroundPadHorizontal + borderSizeX, y = usernameVerticalPad - usernameVerticalOffset) { + usernameLayout.draw(this) + } // Draw the help text val helpTextPaint = TextPaint().apply { From c9f5f91aad8559998821b5e6269372609b3b2842 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Thu, 22 Feb 2024 12:48:51 -0500 Subject: [PATCH 08/18] Fix black bars on username scan crosshair. --- .../main/UsernameQrScanScreen.kt | 67 +++++++++++-------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/UsernameQrScanScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/UsernameQrScanScreen.kt index fc4ecc0924..42052a36e6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/UsernameQrScanScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/UsernameQrScanScreen.kt @@ -9,16 +9,15 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithContent -import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.PathEffect import androidx.compose.ui.graphics.drawscope.DrawScope -import androidx.compose.ui.graphics.drawscope.Fill import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource @@ -46,13 +45,17 @@ fun UsernameQrScanScreen( onQrResultHandled: () -> Unit, modifier: Modifier = Modifier ) { + val path = remember { Path() } + when (qrScanResult) { QrScanResult.InvalidData -> { QrScanResultDialog(stringResource(R.string.UsernameLinkSettings_qr_result_invalid), onDismiss = onQrResultHandled) } + QrScanResult.NetworkError -> { QrScanResultDialog(stringResource(R.string.UsernameLinkSettings_qr_result_network_error), onDismiss = onQrResultHandled) } + is QrScanResult.NotFound -> { if (qrScanResult.username != null) { QrScanResultDialog(stringResource(R.string.UsernameLinkSettings_qr_result_not_found, qrScanResult.username), onDismiss = onQrResultHandled) @@ -60,10 +63,12 @@ fun UsernameQrScanScreen( QrScanResultDialog(stringResource(R.string.UsernameLinkSettings_qr_result_not_found_no_username), onDismiss = onQrResultHandled) } } + is QrScanResult.Success -> { CommunicationActions.startConversation(LocalContext.current, qrScanResult.recipient, null) onQrResultHandled() } + null -> {} } @@ -88,7 +93,7 @@ fun UsernameQrScanScreen( .weight(1f, true) .drawWithContent { drawContent() - drawQrCrosshair() + drawQrCrosshair(path) } ) @@ -117,33 +122,39 @@ private fun QrScanResultDialog(message: String, onDismiss: () -> Unit) { ) } -private fun DrawScope.drawQrCrosshair() { +private fun DrawScope.drawQrCrosshair(path: Path) { val crosshairWidth: Float = size.minDimension * 0.6f - val clearWidth: Float = crosshairWidth * 0.75f + val crosshairLineLength = crosshairWidth * 0.125f - // Draw a full white rounded rect... - drawRoundRect( - color = Color.White, - topLeft = center - Offset(crosshairWidth / 2, crosshairWidth / 2), - style = Stroke(width = 3.dp.toPx()), - size = Size(crosshairWidth, crosshairWidth), - cornerRadius = CornerRadius(10.dp.toPx(), 10.dp.toPx()) - ) + val topLeft = center - Offset(crosshairWidth / 2, crosshairWidth / 2) + val topRight = center + Offset(crosshairWidth / 2, -crosshairWidth / 2) + val bottomRight = center + Offset(crosshairWidth / 2, crosshairWidth / 2) + val bottomLeft = center + Offset(-crosshairWidth / 2, crosshairWidth / 2) - // ...then cut out the middle parts with BlendMode.Clear to leave us with just the corners - drawRect( - color = Color.White, - topLeft = Offset(center.x - clearWidth / 2, 0f), - style = Fill, - size = Size(clearWidth, size.height), - blendMode = BlendMode.Clear - ) + path.reset() + + drawPath( + path = path.apply { + moveTo(topLeft.x, topLeft.y + crosshairLineLength) + lineTo(topLeft.x, topLeft.y) + lineTo(topLeft.x + crosshairLineLength, topLeft.y) - drawRect( + moveTo(topRight.x - crosshairLineLength, topRight.y) + lineTo(topRight.x, topRight.y) + lineTo(topRight.x, topRight.y + crosshairLineLength) + + moveTo(bottomRight.x, bottomRight.y - crosshairLineLength) + lineTo(bottomRight.x, bottomRight.y) + lineTo(bottomRight.x - crosshairLineLength, bottomRight.y) + + moveTo(bottomLeft.x + crosshairLineLength, bottomLeft.y) + lineTo(bottomLeft.x, bottomLeft.y) + lineTo(bottomLeft.x, bottomLeft.y - crosshairLineLength) + }, color = Color.White, - topLeft = Offset(0f, center.y - clearWidth / 2), - style = Fill, - size = Size(size.width, clearWidth), - blendMode = BlendMode.Clear + style = Stroke( + width = 3.dp.toPx(), + pathEffect = PathEffect.cornerPathEffect(10.dp.toPx()) + ) ) } From b147882e4f423040a330ed9fcfe5c379f123e6bf Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Thu, 22 Feb 2024 13:16:10 -0500 Subject: [PATCH 09/18] Fix text input selection handle colors. --- .../main/res/layout/credit_card_fragment.xml | 3 +++ .../res/layout/username_edit_fragment.xml | 19 ++++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/app/src/main/res/layout/credit_card_fragment.xml b/app/src/main/res/layout/credit_card_fragment.xml index 14606fdc89..82659a09fa 100644 --- a/app/src/main/res/layout/credit_card_fragment.xml +++ b/app/src/main/res/layout/credit_card_fragment.xml @@ -38,6 +38,7 @@ android:layout_marginHorizontal="24dp" android:layout_marginTop="36dp" android:hint="@string/CreditCardFragment__card_number" + android:theme="@style/Signal.ThemeOverlay.TextInputLayout" app:errorEnabled="true" app:layout_constraintTop_toBottomOf="@id/description"> @@ -65,6 +66,7 @@ android:layout_marginTop="18dp" android:hint="@string/CreditCardFragment__mm_yy" android:paddingEnd="18dp" + android:theme="@style/Signal.ThemeOverlay.TextInputLayout" app:errorEnabled="true" app:layout_constraintEnd_toStartOf="@id/card_cvv_wrapper" app:layout_constraintStart_toStartOf="parent" @@ -92,6 +94,7 @@ android:layout_marginEnd="24dp" android:hint="@string/CreditCardFragment__cvv" android:paddingStart="18dp" + android:theme="@style/Signal.ThemeOverlay.TextInputLayout" app:errorEnabled="true" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/card_expiry_wrapper" diff --git a/app/src/main/res/layout/username_edit_fragment.xml b/app/src/main/res/layout/username_edit_fragment.xml index 2a0da0e844..d67d9451c3 100644 --- a/app/src/main/res/layout/username_edit_fragment.xml +++ b/app/src/main/res/layout/username_edit_fragment.xml @@ -39,11 +39,11 @@ android:id="@+id/username_box_fill" android:layout_width="0dp" android:layout_height="0dp" - android:background="@drawable/username_edit_box_fill" android:layout_marginRight="@dimen/dsl_settings_gutter" + android:background="@drawable/username_edit_box_fill" app:layout_constraintBottom_toBottomOf="@id/username_text_wrapper" - app:layout_constraintRight_toRightOf="parent" app:layout_constraintLeft_toLeftOf="@id/username_text_wrapper" + app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="@id/username_text_wrapper" /> @@ -74,9 +75,9 @@ android:imeOptions="actionNext" android:importantForAutofill="no" android:inputType="text" + android:layoutDirection="ltr" android:maxLines="1" - android:minHeight="56dp" - android:layoutDirection="ltr"> + android:minHeight="56dp"> @@ -130,8 +131,8 @@ android:layout_height="2dp" android:background="@color/signal_colorPrimary" app:layout_constraintBottom_toBottomOf="@id/username_text_wrapper" - app:layout_constraintRight_toRightOf="@id/discriminator_text" - app:layout_constraintLeft_toLeftOf="@id/username_text_wrapper" /> + app:layout_constraintLeft_toLeftOf="@id/username_text_wrapper" + app:layout_constraintRight_toRightOf="@id/discriminator_text" /> @@ -159,8 +160,8 @@ android:text="@string/UsernameEditFragment__choose_your_username" android:textAppearance="@style/Signal.Text.BodyLarge" android:textColor="@color/signal_colorOnSurfaceVariant" - app:layout_constraintRight_toRightOf="parent" app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/icon" /> Date: Thu, 22 Feb 2024 13:48:08 -0500 Subject: [PATCH 10/18] Show rate limit specific error message on username reservation. --- .../profiles/manage/UsernameEditFragment.java | 3 +++ .../profiles/manage/UsernameEditViewModel.kt | 14 ++++++++++++-- .../profiles/manage/UsernameRepository.kt | 6 +++++- app/src/main/res/values/strings.xml | 2 ++ 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditFragment.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditFragment.java index da9f3e5195..4321e6abf8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditFragment.java @@ -317,6 +317,9 @@ private void onEvent(@NonNull UsernameEditViewModel.Event event) { case NETWORK_FAILURE: Toast.makeText(requireContext(), R.string.UsernameEditFragment_encountered_a_network_error, Toast.LENGTH_SHORT).show(); break; + case RATE_LIMIT_EXCEEDED: + Toast.makeText(requireContext(), R.string.UsernameEditFragment_rate_limit_exceeded_error, Toast.LENGTH_SHORT).show(); + break; case SKIPPED: closeScreen(); break; diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.kt index 8806517e9a..9dd7f024e3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.kt @@ -34,7 +34,7 @@ import java.util.concurrent.TimeUnit * A note on naming conventions: * Usernames are made up of two discrete components, a nickname and a discriminator. They are formatted thusly: * - * [nickname].[discriminator] + * nickname.discriminator */ internal class UsernameEditViewModel private constructor(private val mode: UsernameEditMode) : ViewModel() { private val events: PublishSubject = PublishSubject.create() @@ -207,6 +207,11 @@ internal class UsernameEditViewModel private constructor(private val mode: Usern uiState.update { State(ButtonState.SUBMIT, UsernameStatus.NONE, it.usernameState) } events.onNext(Event.NETWORK_FAILURE) } + + UsernameSetResult.RATE_LIMIT_ERROR -> { + uiState.update { State(ButtonState.SUBMIT, UsernameStatus.NONE, it.usernameState) } + events.onNext(Event.RATE_LIMIT_EXCEEDED) + } } } } @@ -343,6 +348,11 @@ internal class UsernameEditViewModel private constructor(private val mode: Usern events.onNext(Event.NETWORK_FAILURE) } + UsernameSetResult.RATE_LIMIT_ERROR -> { + uiState.update { State(ButtonState.SUBMIT, UsernameStatus.NONE, UsernameState.NoUsername) } + events.onNext(Event.RATE_LIMIT_EXCEEDED) + } + UsernameSetResult.CANDIDATE_GENERATION_ERROR -> { // TODO -- Retry uiState.update { State(ButtonState.SUBMIT_DISABLED, UsernameStatus.TAKEN, UsernameState.NoUsername) } @@ -380,7 +390,7 @@ internal class UsernameEditViewModel private constructor(private val mode: Usern } enum class Event { - NETWORK_FAILURE, SUBMIT_SUCCESS, DELETE_SUCCESS, SUBMIT_FAIL_INVALID, SUBMIT_FAIL_TAKEN, SKIPPED, NEEDS_CONFIRM_RESET + NETWORK_FAILURE, SUBMIT_SUCCESS, DELETE_SUCCESS, SUBMIT_FAIL_INVALID, SUBMIT_FAIL_TAKEN, SKIPPED, NEEDS_CONFIRM_RESET, RATE_LIMIT_EXCEEDED } class Factory(private val mode: UsernameEditMode) : ViewModelProvider.Factory { diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameRepository.kt index 59bb796783..27dd0d60a5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameRepository.kt @@ -23,6 +23,7 @@ import org.whispersystems.signalservice.api.SignalServiceAccountManager import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.api.push.UsernameLinkComponents import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException +import org.whispersystems.signalservice.api.push.exceptions.RateLimitException import org.whispersystems.signalservice.api.push.exceptions.UsernameIsNotAssociatedWithAnAccountException import org.whispersystems.signalservice.api.push.exceptions.UsernameIsNotReservedException import org.whispersystems.signalservice.api.push.exceptions.UsernameMalformedException @@ -379,6 +380,9 @@ object UsernameRepository { } catch (e: UsernameMalformedException) { Log.w(TAG, "[reserveUsername] Username malformed.") failure(UsernameSetResult.USERNAME_INVALID) + } catch (e: RateLimitException) { + Log.w(TAG, "[reserveUsername] Rate limit exceeded.") + failure(UsernameSetResult.RATE_LIMIT_ERROR) } catch (e: IOException) { Log.w(TAG, "[reserveUsername] Generic network exception.", e) failure(UsernameSetResult.NETWORK_ERROR) @@ -501,7 +505,7 @@ object UsernameRepository { } enum class UsernameSetResult { - SUCCESS, USERNAME_UNAVAILABLE, USERNAME_INVALID, NETWORK_ERROR, CANDIDATE_GENERATION_ERROR + SUCCESS, USERNAME_UNAVAILABLE, USERNAME_INVALID, NETWORK_ERROR, CANDIDATE_GENERATION_ERROR, RATE_LIMIT_ERROR } enum class UsernameReclaimResult { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 682b1daea3..8e54b279a3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2298,6 +2298,8 @@ Delete Successfully removed username. Encountered a network error. + + Too many attempts made, please try again later. This username is taken. Usernames can only include a–Z, 0–9, and underscores. Usernames cannot begin with a number. From 763e891dfdbb2afd6ff29df1799a9053209a54bc Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Thu, 22 Feb 2024 14:23:36 -0500 Subject: [PATCH 11/18] Show username in group invite flow. --- .../database/model/GroupsV2UpdateMessageProducer.java | 2 +- .../java/org/thoughtcrime/securesms/recipients/Recipient.java | 2 +- .../thoughtcrime/securesms/recipients/ui/about/AboutSheet.kt | 4 ++-- .../ui/bottomsheet/RecipientBottomSheetDialogFragment.java | 2 +- .../database/model/GroupsV2UpdateMessageProducerTest.java | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) 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 bb0fe2d825..f41da6c02d 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 @@ -1541,7 +1541,7 @@ private UpdateDescription updateDescription(@PluralsRes int stringRes, String beforeChunk = template.substring(startIndex, nearestPosition); builder.append(beforeChunk); - builder.append(SpanUtil.clickable(Recipient.resolved(recipientId).getDisplayName(context), ContextCompat.getColor(context, R.color.conversation_item_update_text_color), v -> { + builder.append(SpanUtil.clickable(Recipient.resolved(recipientId).getDisplayNameOrUsername(context), ContextCompat.getColor(context, R.color.conversation_item_update_text_color), v -> { if (!recipientId.isUnknown() && clickHandler != null) { clickHandler.accept(recipientId); } 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 35d86e47e6..6452bc06c6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java @@ -652,7 +652,7 @@ public boolean hasNonUsernameDisplayName(@NonNull Context context) { String name = Util.getFirstNonEmpty(getGroupName(context), getSystemProfileName().getGivenName(), getProfileName().getGivenName(), - getE164().orElse(null), + shouldShowE164() ? getE164().orElse(null) : null, getUsername().orElse(null), getDisplayName(context)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/about/AboutSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/about/AboutSheet.kt index 4db9e45db2..da9954ccaa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/about/AboutSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/about/AboutSheet.kt @@ -89,8 +89,8 @@ class AboutSheet : ComposeBottomSheetDialogFragment() { model = AboutModel( isSelf = recipient.get().isSelf, hasAvatar = recipient.get().profileAvatarFileDetails.hasFile(), - displayName = recipient.get().getDisplayName(requireContext()), - shortName = recipient.get().getShortDisplayName(requireContext()), + displayName = recipient.get().getDisplayNameOrUsername(requireContext()), + shortName = recipient.get().getShortDisplayNameIncludingUsername(requireContext()), about = recipient.get().about, verified = verified, recipientForAvatar = recipient.get(), diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientBottomSheetDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientBottomSheetDialogFragment.java index 3f5759dfee..6e76866ad4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientBottomSheetDialogFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientBottomSheetDialogFragment.java @@ -184,7 +184,7 @@ public void onViewCreated(@NonNull View fragmentView, @Nullable Bundle savedInst } String name = recipient.isSelf() ? requireContext().getString(R.string.note_to_self) - : recipient.getDisplayName(requireContext()); + : recipient.getDisplayNameOrUsername(requireContext()); fullName.setVisibility(TextUtils.isEmpty(name) ? View.GONE : View.VISIBLE); SpannableStringBuilder nameBuilder = new SpannableStringBuilder(name); if (recipient.showVerified()) { diff --git a/app/src/test/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducerTest.java b/app/src/test/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducerTest.java index 86a926d9bf..72cd7a2048 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducerTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducerTest.java @@ -98,7 +98,7 @@ public void setup() { private static Recipient recipientWithName(RecipientId id, String name) { Recipient recipient = mock(Recipient.class); when(recipient.getId()).thenReturn(id); - when(recipient.getDisplayName(any())).thenReturn(name); + when(recipient.getDisplayNameOrUsername(any())).thenReturn(name); return recipient; } From dcd0d433b015dcb7ae06811efc33dda468f305ef Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Fri, 23 Feb 2024 12:19:59 -0500 Subject: [PATCH 12/18] Fix potential charset crash on some devices. --- .../signalservice/internal/push/PushServiceSocket.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java index f5f0545427..dd7199e895 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java @@ -1095,7 +1095,7 @@ private Single> createBackupAuthCheckSi */ public @NonNull ACI getAciByUsernameHash(String usernameHash) throws IOException { String response = makeServiceRequestWithoutAuthentication( - String.format(GET_USERNAME_PATH, URLEncoder.encode(usernameHash, StandardCharsets.UTF_8.toString())), + String.format(GET_USERNAME_PATH, URLEncoder.encode(usernameHash, StandardCharsets.UTF_8.name())), "GET", null, NO_HEADERS, From 43caaf7efc72fb10f30164b84482170e8fcb53ab Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Fri, 23 Feb 2024 14:57:38 -0500 Subject: [PATCH 13/18] Update a specific recipient case to merge rather than just steal PNI. --- .../RecipientTableTest_getAndPossiblyMerge.kt | 11 +++++++++++ .../thoughtcrime/securesms/database/RecipientTable.kt | 8 ++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest_getAndPossiblyMerge.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest_getAndPossiblyMerge.kt index 8c24af39c6..4c13e3d3f4 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest_getAndPossiblyMerge.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest_getAndPossiblyMerge.kt @@ -788,6 +788,17 @@ class RecipientTableTest_getAndPossiblyMerge { expectChangeNumberEvent() } + test("merge, e164 follows pni+aci") { + given(E164_A, PNI_A, null) + given(null, null, ACI_A) + + process(null, PNI_A, ACI_A, pniVerified = true) + + expect(E164_A, PNI_A, ACI_A) + expectThreadMergeEvent(E164_A) + expectPniVerified() + } + test("local user, local e164 and aci provided, changeSelf=false, leave e164 alone") { given(E164_SELF, null, ACI_SELF) given(null, null, ACI_A) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt index a9cb71615c..80383c492c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt @@ -2944,10 +2944,10 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da primaryId = data.byAci, secondaryId = data.byPni ) - } else if (data.pniRecord.aci == null && data.pniRecord.e164 == data.e164) { + } else if (data.pniRecord.aci == null && (data.e164 == null || data.pniRecord.e164 == data.e164)) { // The PNI record also has the E164 on it with no ACI. We're going to be stealing all of it's fields, // so this is basically a merge with a little bit of extra prep. - breadCrumbs += "PniRecordHasMatchingE164AndNoAci" + breadCrumbs += "PniRecordHasNoAci" if (data.aciRecord.pni != null) { operations += PnpOperation.RemovePni(data.byAci) @@ -2972,10 +2972,10 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da secondaryId = data.byPni ) } else { - // The PNI record either has an ACI or a non-matching e164, meaning we need to steal what we need and leave the rest behind + // The PNI record has a different ACI, meaning we need to steal what we need and leave the rest behind breadCrumbs += if (data.pniRecord.aci != null && data.pniRecord.e164 != data.e164) { - "PniRecordHasAciAndNonMatchingE164" + "PniRecordHasAci" } else if (data.pniRecord.aci != null) { "PniRecordHasAci" } else { From dc32e51ac276c6e32659acadef76b0703d184b2e Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Fri, 23 Feb 2024 15:19:18 -0500 Subject: [PATCH 14/18] Make a specific crash more clear to improve debuggability. --- .../thoughtcrime/securesms/jobs/PnpInitializeDevicesJob.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PnpInitializeDevicesJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/PnpInitializeDevicesJob.kt index 2fe83fe25a..27c52d36bc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PnpInitializeDevicesJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PnpInitializeDevicesJob.kt @@ -98,10 +98,11 @@ class PnpInitializeDevicesJob private constructor(parameters: Parameters) : Base try { Log.i(TAG, "Initializing PNI for linked devices") - initializeDevices(e164) + val result: VerifyResponseWithoutKbs = initializeDevices(e164) .map(::VerifyResponseWithoutKbs) .safeBlockingGet() - .resultOrThrow + + result.error?.let { throw it } } catch (e: InterruptedException) { throw IOException("Retry", e) } catch (t: Throwable) { From c4842ae7c5138619dae9c89eefcf55bf969a8ea5 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Fri, 23 Feb 2024 15:36:05 -0500 Subject: [PATCH 15/18] Attempt to prevent message retry loops. --- .../securesms/testing/AliceClient.kt | 2 +- .../app/internal/InternalSettingsFragment.kt | 71 ++++++++++------ .../securesms/database/KyberPreKeyTable.kt | 5 ++ .../securesms/database/OneTimePreKeyTable.kt | 5 ++ .../securesms/database/SignedPreKeyTable.kt | 5 ++ .../securesms/jobmanager/JobController.java | 9 ++ .../securesms/jobmanager/JobManager.java | 19 +++++ .../securesms/jobs/PreKeysSyncJob.kt | 82 +++++++++++++------ .../keyvalue/MiscellaneousValues.java | 15 ++++ .../messages/IncomingMessageObserver.kt | 7 +- .../securesms/messages/MessageDecryptor.kt | 82 +++++++++++++++---- .../securesms/util/FeatureFlags.java | 32 +++++++- .../securesms/util/JobExtensions.kt | 15 ++++ app/src/main/protowire/JobData.proto | 4 + 14 files changed, 280 insertions(+), 73 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/util/JobExtensions.kt diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/AliceClient.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/AliceClient.kt index 8eaf4ab865..04d09283de 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/AliceClient.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/AliceClient.kt @@ -40,7 +40,7 @@ class AliceClient(val serviceId: ServiceId, val e164: String, val trustRoot: ECK ApplicationDependencies.getIncomingMessageObserver() .processEnvelope(bufferedStore, envelope, serverDeliveredTimestamp) ?.mapNotNull { it.run() } - ?.forEach { ApplicationDependencies.getJobManager().add(it) } + ?.forEach { it.enqueue() } bufferedStore.flushToDisk() val end = System.currentTimeMillis() diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt index a30ccc0d86..95d767e822 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt @@ -186,6 +186,48 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter } ) + clickPref( + title = DSLSettingsText.from("Log dump PreKey ServiceId-KeyIds"), + onClick = { + logPreKeyIds() + } + ) + + clickPref( + title = DSLSettingsText.from("Retry all jobs now"), + summary = DSLSettingsText.from("Clear backoff intervals, app will restart"), + onClick = { + SimpleTask.run({ + JobDatabase.getInstance(ApplicationDependencies.getApplication()).debugResetBackoffInterval() + }) { + AppUtil.restart(requireContext()) + } + } + ) + + clickPref( + title = DSLSettingsText.from("Delete all prekeys"), + summary = DSLSettingsText.from("Deletes all signed/last-resort/one-time prekeys for both ACI and PNI accounts. WILL cause problems."), + onClick = { + MaterialAlertDialogBuilder(requireContext()) + .setTitle("Delete all prekeys?") + .setMessage("Are you sure? This will delete all prekeys for both ACI and PNI accounts. This WILL cause problems.") + .setPositiveButton(android.R.string.ok) { _, _ -> + SignalDatabase.signedPreKeys.debugDeleteAll() + SignalDatabase.oneTimePreKeys.debugDeleteAll() + SignalDatabase.kyberPreKeys.debugDeleteAll() + + Toast.makeText(requireContext(), "All prekeys deleted!", Toast.LENGTH_SHORT).show() + } + .setNegativeButton(android.R.string.cancel, null) + .show() + } + ) + + dividerPref() + + sectionHeaderPref(DSLSettingsText.from("Logging")) + clickPref( title = DSLSettingsText.from("Clear all logs"), onClick = { @@ -227,21 +269,10 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter ) clickPref( - title = DSLSettingsText.from("Log dump PreKey ServiceId-KeyIds"), - onClick = { - logPreKeyIds() - } - ) - - clickPref( - title = DSLSettingsText.from("Retry all jobs now"), - summary = DSLSettingsText.from("Clear backoff intervals, app will restart"), + title = DSLSettingsText.from("Clear local metrics"), + summary = DSLSettingsText.from("Click to clear all local metrics state."), onClick = { - SimpleTask.run({ - JobDatabase.getInstance(ApplicationDependencies.getApplication()).debugResetBackoffInterval() - }) { - AppUtil.restart(requireContext()) - } + clearAllLocalMetricsState() } ) @@ -436,18 +467,6 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter dividerPref() - sectionHeaderPref(DSLSettingsText.from("Local Metrics")) - - clickPref( - title = DSLSettingsText.from("Clear local metrics"), - summary = DSLSettingsText.from("Click to clear all local metrics state."), - onClick = { - clearAllLocalMetricsState() - } - ) - - dividerPref() - sectionHeaderPref(DSLSettingsText.from("Group call server")) radioPref( diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/KyberPreKeyTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/KyberPreKeyTable.kt index 558ba3e241..bec3b247dc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/KyberPreKeyTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/KyberPreKeyTable.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.database import android.content.Context import org.signal.core.util.delete +import org.signal.core.util.deleteAll import org.signal.core.util.exists import org.signal.core.util.insertInto import org.signal.core.util.logging.Log @@ -171,6 +172,10 @@ class KyberPreKeyTable(context: Context, databaseHelper: SignalDatabase) : Datab Log.i(TAG, "Deleted $count stale one-time EC prekeys.") } + fun debugDeleteAll() { + writableDatabase.deleteAll(OneTimePreKeyTable.TABLE_NAME) + } + data class KyberPreKey( val record: KyberPreKeyRecord, val lastResort: Boolean diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/OneTimePreKeyTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/OneTimePreKeyTable.kt index 1cac549f05..1cf59d43ec 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/OneTimePreKeyTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/OneTimePreKeyTable.kt @@ -5,6 +5,7 @@ import androidx.core.content.contentValuesOf import org.signal.core.util.Base64 import org.signal.core.util.SqlUtil import org.signal.core.util.delete +import org.signal.core.util.deleteAll import org.signal.core.util.logging.Log import org.signal.core.util.requireNonNullString import org.signal.core.util.update @@ -115,6 +116,10 @@ class OneTimePreKeyTable(context: Context, databaseHelper: SignalDatabase) : Dat Log.i(TAG, "Deleted $count stale one-time EC prekeys.") } + fun debugDeleteAll() { + writableDatabase.deleteAll(TABLE_NAME) + } + private fun ServiceId.toAccountId(): String { return when (this) { is ServiceId.ACI -> this.toString() diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SignedPreKeyTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SignedPreKeyTable.kt index 3396b316dc..7679d26737 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SignedPreKeyTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SignedPreKeyTable.kt @@ -4,6 +4,7 @@ import android.content.Context import androidx.core.content.contentValuesOf import org.signal.core.util.Base64 import org.signal.core.util.SqlUtil +import org.signal.core.util.deleteAll import org.signal.core.util.logging.Log import org.signal.core.util.requireInt import org.signal.core.util.requireLong @@ -103,6 +104,10 @@ class SignedPreKeyTable(context: Context, databaseHelper: SignalDatabase) : Data writableDatabase.delete(TABLE_NAME, "$ACCOUNT_ID = ? AND $KEY_ID = ?", SqlUtil.buildArgs(serviceId.toAccountId(), keyId)) } + fun debugDeleteAll() { + writableDatabase.deleteAll(OneTimePreKeyTable.TABLE_NAME) + } + private fun ServiceId.toAccountId(): String { return when (this) { is ServiceId.ACI -> this.toString() diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java index 73a068e546..e0e2bb5e90 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java @@ -76,6 +76,15 @@ synchronized void wakeUp() { notifyAll(); } + @WorkerThread + void submitNewJobChains(@NonNull List>> chains) { + synchronized (this) { + for (List> chain : chains) { + submitNewJobChain(chain); + } + } + } + @WorkerThread void submitNewJobChain(@NonNull List> chain) { synchronized (this) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java index 834ebe45e2..ffa70fbae0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java @@ -35,6 +35,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; +import java.util.stream.Collectors; /** * Allows the scheduling of durable jobs that will be run as early as possible. @@ -208,6 +209,24 @@ public void addAll(@NonNull List jobs) { }); } + public void addAllChains(@NonNull List chains) { + if (chains.isEmpty()) { + return; + } + + for (Chain chain : chains) { + for (List jobList : chain.getJobListChain()) { + for (Job job : jobList) { + jobTracker.onStateChange(job, JobTracker.JobState.PENDING); + } + } + } + + runOnExecutor(() -> { + jobController.submitNewJobChains(chains.stream().map(Chain::getJobListChain).collect(Collectors.toList())); + }); + } + /** * Begins the creation of a job chain with a single job. * @see Chain diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PreKeysSyncJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/PreKeysSyncJob.kt index 3f5f60bc67..f3b51d7a59 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PreKeysSyncJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PreKeysSyncJob.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.jobs import androidx.annotation.VisibleForTesting import org.signal.core.util.logging.Log +import org.signal.core.util.roundedString import org.signal.libsignal.protocol.state.KyberPreKeyRecord import org.signal.libsignal.protocol.state.PreKeyRecord import org.signal.libsignal.protocol.state.SignalProtocolStore @@ -11,7 +12,9 @@ import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.jobmanager.Job import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint +import org.thoughtcrime.securesms.jobs.protos.PreKeysSyncJobData import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.util.FeatureFlags import org.whispersystems.signalservice.api.SignalServiceAccountDataStore import org.whispersystems.signalservice.api.account.PreKeyUpload import org.whispersystems.signalservice.api.push.ServiceId @@ -34,7 +37,10 @@ import kotlin.time.DurationUnit * It will also rotate/create last-resort kyber prekeys for both ACI and PNI identities, as well as ensure * that the user has a sufficient number of one-time kyber prekeys available on the service. */ -class PreKeysSyncJob private constructor(parameters: Parameters) : BaseJob(parameters) { +class PreKeysSyncJob private constructor( + parameters: Parameters, + private val forceRotationRequested: Boolean +) : BaseJob(parameters) { companion object { const val KEY = "PreKeysSyncJob" @@ -52,9 +58,13 @@ class PreKeysSyncJob private constructor(parameters: Parameters) : BaseJob(param @JvmField val MAXIMUM_ALLOWED_SIGNED_PREKEY_AGE = 14.days.inWholeMilliseconds + /** + * @param forceRotationRequested If true, this will force the rotation of all keys, provided we haven't already done a forced refresh recently. + */ + @JvmOverloads @JvmStatic - fun create(): PreKeysSyncJob { - return PreKeysSyncJob() + fun create(forceRotationRequested: Boolean = false): PreKeysSyncJob { + return PreKeysSyncJob(forceRotationRequested) } @JvmStatic @@ -87,19 +97,22 @@ class PreKeysSyncJob private constructor(parameters: Parameters) : BaseJob(param } @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - constructor() : this( + constructor(forceRotation: Boolean = false) : this( Parameters.Builder() .setQueue("PreKeysSyncJob") .addConstraint(NetworkConstraint.KEY) .setMaxInstancesForFactory(1) .setMaxAttempts(Parameters.UNLIMITED) .setLifespan(TimeUnit.DAYS.toMillis(30)) - .build() + .build(), + forceRotation ) override fun getFactoryKey(): String = KEY - override fun serialize(): ByteArray? = null + override fun serialize(): ByteArray { + return PreKeysSyncJobData(forceRotationRequested).encode() + } override fun onRun() { if (!SignalStore.account().isRegistered || SignalStore.account().aci == null || SignalStore.account().pni == null) { @@ -107,12 +120,30 @@ class PreKeysSyncJob private constructor(parameters: Parameters) : BaseJob(param return } - syncPreKeys(ServiceIdType.ACI, SignalStore.account().aci, ApplicationDependencies.getProtocolStore().aci(), SignalStore.account().aciPreKeys) - syncPreKeys(ServiceIdType.PNI, SignalStore.account().pni, ApplicationDependencies.getProtocolStore().pni(), SignalStore.account().pniPreKeys) + val forceRotation = if (forceRotationRequested) { + val timeSinceLastForcedRotation = System.currentTimeMillis() - SignalStore.misc().lastForcedPreKeyRefresh + // We check < 0 in case someone changed their clock and had a bad value set + timeSinceLastForcedRotation > FeatureFlags.preKeyForceRefreshInterval() || timeSinceLastForcedRotation < 0 + } else { + false + } + + if (forceRotation) { + warn(TAG, "Forcing prekey rotation.") + } else if (forceRotationRequested) { + warn(TAG, "Forced prekey rotation was requested, but we already did a forced refresh ${System.currentTimeMillis() - SignalStore.misc().lastForcedPreKeyRefresh} ms ago. Ignoring.") + } + + syncPreKeys(ServiceIdType.ACI, SignalStore.account().aci, ApplicationDependencies.getProtocolStore().aci(), SignalStore.account().aciPreKeys, forceRotation) + syncPreKeys(ServiceIdType.PNI, SignalStore.account().pni, ApplicationDependencies.getProtocolStore().pni(), SignalStore.account().pniPreKeys, forceRotation) SignalStore.misc().lastFullPrekeyRefreshTime = System.currentTimeMillis() + + if (forceRotation) { + SignalStore.misc().lastForcedPreKeyRefresh = System.currentTimeMillis() + } } - private fun syncPreKeys(serviceIdType: ServiceIdType, serviceId: ServiceId?, protocolStore: SignalServiceAccountDataStore, metadataStore: PreKeyMetadataStore) { + private fun syncPreKeys(serviceIdType: ServiceIdType, serviceId: ServiceId?, protocolStore: SignalServiceAccountDataStore, metadataStore: PreKeyMetadataStore, forceRotation: Boolean) { if (serviceId == null) { warn(TAG, serviceIdType, "AccountId not set!") return @@ -121,20 +152,20 @@ class PreKeysSyncJob private constructor(parameters: Parameters) : BaseJob(param val accountManager = ApplicationDependencies.getSignalServiceAccountManager() val availablePreKeyCounts: OneTimePreKeyCounts = accountManager.getPreKeyCounts(serviceIdType) - val signedPreKeyToUpload: SignedPreKeyRecord? = signedPreKeyUploadIfNeeded(serviceIdType, protocolStore, metadataStore) + val signedPreKeyToUpload: SignedPreKeyRecord? = signedPreKeyUploadIfNeeded(serviceIdType, protocolStore, metadataStore, forceRotation) - val oneTimeEcPreKeysToUpload: List? = if (availablePreKeyCounts.ecCount < ONE_TIME_PREKEY_MINIMUM) { - log(serviceIdType, "There are ${availablePreKeyCounts.ecCount} one-time EC prekeys available, which is less than our threshold. Need more.") + val oneTimeEcPreKeysToUpload: List? = if (forceRotation || availablePreKeyCounts.ecCount < ONE_TIME_PREKEY_MINIMUM) { + log(serviceIdType, "There are ${availablePreKeyCounts.ecCount} one-time EC prekeys available, which is less than our threshold. Need more. (Forced: $forceRotation)") PreKeyUtil.generateAndStoreOneTimeEcPreKeys(protocolStore, metadataStore) } else { log(serviceIdType, "There are ${availablePreKeyCounts.ecCount} one-time EC prekeys available, which is enough.") null } - val lastResortKyberPreKeyToUpload: KyberPreKeyRecord? = lastResortKyberPreKeyUploadIfNeeded(serviceIdType, protocolStore, metadataStore) + val lastResortKyberPreKeyToUpload: KyberPreKeyRecord? = lastResortKyberPreKeyUploadIfNeeded(serviceIdType, protocolStore, metadataStore, forceRotation) - val oneTimeKyberPreKeysToUpload: List? = if (availablePreKeyCounts.kyberCount < ONE_TIME_PREKEY_MINIMUM) { - log(serviceIdType, "There are ${availablePreKeyCounts.kyberCount} one-time kyber prekeys available, which is less than our threshold. Need more.") + val oneTimeKyberPreKeysToUpload: List? = if (forceRotation || availablePreKeyCounts.kyberCount < ONE_TIME_PREKEY_MINIMUM) { + log(serviceIdType, "There are ${availablePreKeyCounts.kyberCount} one-time kyber prekeys available, which is less than our threshold. Need more. (Forced: $forceRotation)") PreKeyUtil.generateAndStoreOneTimeKyberPreKeys(protocolStore, metadataStore) } else { log(serviceIdType, "There are ${availablePreKeyCounts.kyberCount} one-time kyber prekeys available, which is enough.") @@ -183,28 +214,28 @@ class PreKeysSyncJob private constructor(parameters: Parameters) : BaseJob(param PreKeyUtil.cleanOneTimePreKeys(protocolStore) } - private fun signedPreKeyUploadIfNeeded(serviceIdType: ServiceIdType, protocolStore: SignalProtocolStore, metadataStore: PreKeyMetadataStore): SignedPreKeyRecord? { + private fun signedPreKeyUploadIfNeeded(serviceIdType: ServiceIdType, protocolStore: SignalProtocolStore, metadataStore: PreKeyMetadataStore, forceRotation: Boolean): SignedPreKeyRecord? { val signedPreKeyRegistered = metadataStore.isSignedPreKeyRegistered && metadataStore.activeSignedPreKeyId >= 0 val timeSinceLastSignedPreKeyRotation = System.currentTimeMillis() - metadataStore.lastSignedPreKeyRotationTime - return if (!signedPreKeyRegistered || timeSinceLastSignedPreKeyRotation >= REFRESH_INTERVAL || timeSinceLastSignedPreKeyRotation < 0) { - log(serviceIdType, "Rotating signed prekey. SignedPreKeyRegistered: $signedPreKeyRegistered, TimeSinceLastRotation: $timeSinceLastSignedPreKeyRotation ms (${timeSinceLastSignedPreKeyRotation.milliseconds.toDouble(DurationUnit.DAYS)} days)") + return if (forceRotation || !signedPreKeyRegistered || timeSinceLastSignedPreKeyRotation >= REFRESH_INTERVAL || timeSinceLastSignedPreKeyRotation < 0) { + log(serviceIdType, "Rotating signed prekey. ForceRotation: $forceRotation, SignedPreKeyRegistered: $signedPreKeyRegistered, TimeSinceLastRotation: $timeSinceLastSignedPreKeyRotation ms (${timeSinceLastSignedPreKeyRotation.milliseconds.toDouble(DurationUnit.DAYS).roundedString(2)} days)") PreKeyUtil.generateAndStoreSignedPreKey(protocolStore, metadataStore) } else { - log(serviceIdType, "No need to rotate signed prekey. TimeSinceLastRotation: $timeSinceLastSignedPreKeyRotation ms (${timeSinceLastSignedPreKeyRotation.milliseconds.toDouble(DurationUnit.DAYS)} days)") + log(serviceIdType, "No need to rotate signed prekey. TimeSinceLastRotation: $timeSinceLastSignedPreKeyRotation ms (${timeSinceLastSignedPreKeyRotation.milliseconds.toDouble(DurationUnit.DAYS).roundedString(2)} days)") null } } - private fun lastResortKyberPreKeyUploadIfNeeded(serviceIdType: ServiceIdType, protocolStore: SignalServiceAccountDataStore, metadataStore: PreKeyMetadataStore): KyberPreKeyRecord? { + private fun lastResortKyberPreKeyUploadIfNeeded(serviceIdType: ServiceIdType, protocolStore: SignalServiceAccountDataStore, metadataStore: PreKeyMetadataStore, forceRotation: Boolean): KyberPreKeyRecord? { val lastResortRegistered = metadataStore.lastResortKyberPreKeyId >= 0 val timeSinceLastSignedPreKeyRotation = System.currentTimeMillis() - metadataStore.lastResortKyberPreKeyRotationTime - return if (!lastResortRegistered || timeSinceLastSignedPreKeyRotation >= REFRESH_INTERVAL || timeSinceLastSignedPreKeyRotation < 0) { - log(serviceIdType, "Rotating last-resort kyber prekey. TimeSinceLastRotation: $timeSinceLastSignedPreKeyRotation ms (${timeSinceLastSignedPreKeyRotation.milliseconds.toDouble(DurationUnit.DAYS)} days)") + return if (forceRotation || !lastResortRegistered || timeSinceLastSignedPreKeyRotation >= REFRESH_INTERVAL || timeSinceLastSignedPreKeyRotation < 0) { + log(serviceIdType, "Rotating last-resort kyber prekey. ForceRotation: $forceRotation, TimeSinceLastRotation: $timeSinceLastSignedPreKeyRotation ms (${timeSinceLastSignedPreKeyRotation.milliseconds.toDouble(DurationUnit.DAYS).roundedString(2)} days)") PreKeyUtil.generateAndStoreLastResortKyberPreKey(protocolStore, metadataStore) } else { - log(serviceIdType, "No need to rotate last-resort kyber prekey. TimeSinceLastRotation: $timeSinceLastSignedPreKeyRotation ms (${timeSinceLastSignedPreKeyRotation.milliseconds.toDouble(DurationUnit.DAYS)} days)") + log(serviceIdType, "No need to rotate last-resort kyber prekey. TimeSinceLastRotation: $timeSinceLastSignedPreKeyRotation ms (${timeSinceLastSignedPreKeyRotation.milliseconds.toDouble(DurationUnit.DAYS).roundedString(2)} days)") null } } @@ -225,7 +256,10 @@ class PreKeysSyncJob private constructor(parameters: Parameters) : BaseJob(param class Factory : Job.Factory { override fun create(parameters: Parameters, serializedData: ByteArray?): PreKeysSyncJob { - return PreKeysSyncJob(parameters) + return serializedData?.let { + val data = PreKeysSyncJobData.ADAPTER.decode(serializedData) + PreKeysSyncJob(parameters, data.forceRefreshRequested) + } ?: PreKeysSyncJob(parameters, forceRotationRequested = false) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.java index ec8df5a8a2..d191b4194e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.java @@ -40,6 +40,7 @@ public final class MiscellaneousValues extends SignalStoreValues { private static final String SERVER_TIME_OFFSET = "misc.server_time_offset"; private static final String LAST_SERVER_TIME_OFFSET_UPDATE = "misc.last_server_time_offset_update"; private static final String NEEDS_USERNAME_RESTORE = "misc.needs_username_restore"; + private static final String LAST_FORCED_PREKEY_REFRESH = "misc.last_forced_prekey_refresh"; MiscellaneousValues(@NonNull KeyValueStore store) { super(store); @@ -344,4 +345,18 @@ public boolean needsUsernameRestore() { public void setNeedsUsernameRestore(boolean value) { putBoolean(NEEDS_USERNAME_RESTORE, value); } + + /** + * Set the last time we successfully completed a forced prekey refresh. + */ + public void setLastForcedPreKeyRefresh(long time) { + putLong(LAST_FORCED_PREKEY_REFRESH, time); + } + + /** + * Get the last time we successfully completed a forced prekey refresh. + */ + public long getLastForcedPreKeyRefresh() { + return getLong(LAST_FORCED_PREKEY_REFRESH, 0); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.kt index f00d210221..0aef80671d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.kt @@ -30,6 +30,7 @@ import org.thoughtcrime.securesms.notifications.NotificationChannels import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.util.AppForegroundObserver import org.thoughtcrime.securesms.util.SignalLocalMetrics +import org.thoughtcrime.securesms.util.asChain import org.whispersystems.signalservice.api.push.ServiceId import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState import org.whispersystems.signalservice.api.websocket.WebSocketUnavailableException @@ -294,7 +295,7 @@ class IncomingMessageObserver(private val context: Application) { is MessageDecryptor.Result.Success -> { val job = PushProcessMessageJob.processOrDefer(messageContentProcessor, result, localReceiveMetric) if (job != null) { - return result.followUpOperations + FollowUpOperation { job } + return result.followUpOperations + FollowUpOperation { job.asChain() } } } is MessageDecryptor.Result.Error -> { @@ -303,7 +304,7 @@ class IncomingMessageObserver(private val context: Application) { result.toMessageState(), result.errorMetadata.toExceptionMetadata(), result.envelope.timestamp!! - ) + ).asChain() } } is MessageDecryptor.Result.Ignore -> { @@ -404,7 +405,7 @@ class IncomingMessageObserver(private val context: Application) { if (followUpOperations != null) { Log.d(TAG, "Running ${followUpOperations.size} follow-up operations...") val jobs = followUpOperations.mapNotNull { it.run() } - ApplicationDependencies.getJobManager().addAll(jobs) + ApplicationDependencies.getJobManager().addAllChains(jobs) } signalWebSocket.sendAck(response) diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt index fe0ca69e20..42f564631e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt @@ -37,7 +37,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.groups.BadGroupIdException import org.thoughtcrime.securesms.groups.GroupId -import org.thoughtcrime.securesms.jobmanager.Job +import org.thoughtcrime.securesms.jobmanager.JobManager import org.thoughtcrime.securesms.jobs.AutomaticSessionResetJob import org.thoughtcrime.securesms.jobs.PreKeysSyncJob import org.thoughtcrime.securesms.jobs.SendRetryReceiptJob @@ -50,6 +50,8 @@ import org.thoughtcrime.securesms.notifications.NotificationIds import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.util.FeatureFlags +import org.thoughtcrime.securesms.util.LRUCache +import org.thoughtcrime.securesms.util.asChain import org.whispersystems.signalservice.api.InvalidMessageStructureException import org.whispersystems.signalservice.api.crypto.ContentHint import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata @@ -77,6 +79,8 @@ object MessageDecryptor { private val TAG = Log.tag(MessageDecryptor::class.java) + private val decryptionErrorCounts: MutableMap = LRUCache(100) + /** * Decrypts an envelope and provides a [Result]. This method has side effects, but all of them are limited to [SignalDatabase]. * That means that this operation should be atomic when performed within a transaction. @@ -125,8 +129,9 @@ object MessageDecryptor { val followUpOperations: MutableList = mutableListOf() if (envelope.type == Envelope.Type.PREKEY_BUNDLE) { + Log.i(TAG, "${logPrefix(envelope)} Prekey message. Scheduling a prekey sync job.") followUpOperations += FollowUpOperation { - PreKeysSyncJob.create() + PreKeysSyncJob.create().asChain() } } @@ -219,7 +224,7 @@ object MessageDecryptor { followUpOperations += FollowUpOperation { val sender: Recipient = Recipient.external(context, e.sender) - AutomaticSessionResetJob(sender.id, e.senderDevice, envelope.timestamp!!) + AutomaticSessionResetJob(sender.id, e.senderDevice, envelope.timestamp!!).asChain() } Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations.toUnmodifiableList()) @@ -277,36 +282,70 @@ object MessageDecryptor { val senderDevice: Int = protocolException.senderDevice val receivedTimestamp: Long = System.currentTimeMillis() val sender: Recipient = Recipient.external(context, protocolException.sender) + val senderServiceId: ServiceId? = ServiceId.parseOrNull(protocolException.sender) if (sender.isSelf) { - Log.w(TAG, "${logPrefix(envelope)} Decryption error for a sync message! Enqueuing a session reset job.") + Log.w(TAG, "${logPrefix(envelope)} Decryption error for a sync message! Enqueuing a session reset job.", true) followUpOperations += FollowUpOperation { - AutomaticSessionResetJob(sender.id, senderDevice, envelope.timestamp!!) + AutomaticSessionResetJob(sender.id, senderDevice, envelope.timestamp!!).asChain() } return Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations) } + val errorCount: DecryptionErrorCount = decryptionErrorCounts.getOrPut(sender.id) { DecryptionErrorCount(count = 0, lastReceivedTime = 0) } + val timeSinceLastError = receivedTimestamp - errorCount.lastReceivedTime + if (timeSinceLastError > FeatureFlags.retryReceiptMaxCountResetAge() && errorCount.count > 0) { + Log.i(TAG, "${logPrefix(envelope, senderServiceId)} Resetting decryption error count for ${sender.id} because it has been $timeSinceLastError ms since the last error.", true) + errorCount.count = 0 + } + + errorCount.count++ + errorCount.lastReceivedTime = receivedTimestamp + + if (errorCount.count > FeatureFlags.retryReceiptMaxCount()) { + Log.w(TAG, "${logPrefix(envelope, senderServiceId)} This is error number ${errorCount.count} from ${sender.id}, which is greater than the maximum of ${FeatureFlags.retryReceiptMaxCount()}. Ignoring.", true) + + if (contentHint == ContentHint.IMPLICIT) { + Log.w(TAG, "${logPrefix(envelope, senderServiceId)} The content hint is $contentHint, so no error message is needed.", true) + Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations) + } else { + Log.w(TAG, "${logPrefix(envelope, senderServiceId)} The content hint is $contentHint, so we need to insert an error right away.", true) + return Result.DecryptionError(envelope, serverDeliveredTimestamp, protocolException.toErrorMetadata(), followUpOperations.toUnmodifiableList()) + } + } else { + Log.w(TAG, "${logPrefix(envelope, senderServiceId)} This is error number ${errorCount.count} from ${sender.id}.${if (errorCount.count > 1) " It has been $timeSinceLastError ms since the last error." else "" }", true) + } + followUpOperations += FollowUpOperation { - buildSendRetryReceiptJob(envelope, protocolException, sender) + val retryJob = buildSendRetryReceiptJob(envelope, protocolException, sender) + + // Note: if the message is sealed sender, it's envelope type will be UNIDENTIFIED_SENDER. The only way we can currently check if the error is + // prekey-related in that situation is using a string match. + if (envelope.type == Envelope.Type.PREKEY_BUNDLE || protocolException.message?.lowercase()?.contains("prekey") == true) { + Log.w(TAG, "${logPrefix(envelope, senderServiceId)} Got a decryption error on a prekey message. Forcing a prekey rotation before requesting the retry.", true) + PreKeysSyncJob.create(forceRotationRequested = true).asChain().then(retryJob) + } else { + retryJob.asChain() + } } return when (contentHint) { ContentHint.DEFAULT -> { - Log.w(TAG, "${logPrefix(envelope)} The content hint is $contentHint, so we need to insert an error right away.", true) + Log.w(TAG, "${logPrefix(envelope, senderServiceId)} The content hint is $contentHint, so we need to insert an error right away.", true) Result.DecryptionError(envelope, serverDeliveredTimestamp, protocolException.toErrorMetadata(), followUpOperations.toUnmodifiableList()) } ContentHint.RESENDABLE -> { - Log.w(TAG, "${logPrefix(envelope)} The content hint is $contentHint, so we can try to resend the message.", true) + Log.w(TAG, "${logPrefix(envelope, senderServiceId)} The content hint is $contentHint, so we can try to resend the message.", true) followUpOperations += FollowUpOperation { val groupId: GroupId? = protocolException.parseGroupId(envelope) val threadId: Long? = if (groupId != null) { if (SignalDatabase.groups.getGroup(groupId).isAbsent()) { - Log.w(TAG, "${logPrefix(envelope)} No group found for $groupId! Not inserting a retry receipt.") + Log.w(TAG, "${logPrefix(envelope, senderServiceId)} No group found for $groupId! Not inserting a retry receipt.") return@FollowUpOperation null } @@ -317,7 +356,7 @@ object MessageDecryptor { } if (threadId == null) { - Log.w(TAG, "${logPrefix(envelope)} Thread does not already exist for sender ${sender.id}! We will not create one just to show a retry receipt.") + Log.w(TAG, "${logPrefix(envelope, senderServiceId)} Thread does not already exist for sender ${sender.id}! We will not create one just to show a retry receipt.") return@FollowUpOperation null } @@ -330,7 +369,7 @@ object MessageDecryptor { } ContentHint.IMPLICIT -> { - Log.w(TAG, "${logPrefix(envelope)} The content hint is $contentHint, so no error message is needed.", true) + Log.w(TAG, "${logPrefix(envelope, senderServiceId)} The content hint is $contentHint, so no error message is needed.", true) Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations) } } @@ -399,20 +438,24 @@ object MessageDecryptor { } private fun logPrefix(envelope: Envelope): String { - return logPrefix(envelope.timestamp!!, envelope.sourceServiceId ?: "", envelope.sourceDevice) + return logPrefix(envelope.timestamp!!, ServiceId.parseOrNull(envelope.sourceServiceId)?.logString() ?: "", envelope.sourceDevice) } - private fun logPrefix(envelope: Envelope, sender: ServiceId): String { - return logPrefix(envelope.timestamp!!, sender.toString(), envelope.sourceDevice) + private fun logPrefix(envelope: Envelope, sender: ServiceId?): String { + return logPrefix(envelope.timestamp!!, sender?.logString() ?: "?", envelope.sourceDevice) + } + + private fun logPrefix(envelope: Envelope, sender: String): String { + return logPrefix(envelope.timestamp!!, ServiceId.parseOrNull(sender)?.logString() ?: "?", envelope.sourceDevice) } private fun logPrefix(envelope: Envelope, cipherResult: SignalServiceCipherResult): String { - return logPrefix(envelope.timestamp!!, cipherResult.metadata.sourceServiceId.toString(), cipherResult.metadata.sourceDeviceId) + return logPrefix(envelope.timestamp!!, cipherResult.metadata.sourceServiceId.logString(), cipherResult.metadata.sourceDeviceId) } private fun logPrefix(envelope: Envelope, exception: ProtocolException): String { return if (exception.sender != null) { - logPrefix(envelope.timestamp!!, exception.sender, exception.senderDevice) + logPrefix(envelope.timestamp!!, ServiceId.parseOrNull(exception.sender)?.logString() ?: "?", exception.senderDevice) } else { logPrefix(envelope.timestamp!!, envelope.sourceServiceId, envelope.sourceDevice) } @@ -546,7 +589,12 @@ object MessageDecryptor { val groupId: GroupId? ) + data class DecryptionErrorCount( + var count: Int, + var lastReceivedTime: Long + ) + fun interface FollowUpOperation { - fun run(): Job? + fun run(): JobManager.Chain? } } 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 ef24bb0972..51c5fb6c66 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java @@ -119,6 +119,9 @@ public final class FeatureFlags { private static final String GIF_SEARCH = "global.gifSearch"; private static final String AUDIO_REMUXING = "android.media.audioRemux.1"; private static final String VIDEO_RECORD_1X_ZOOM = "android.media.videoCaptureDefaultZoom"; + private static final String RETRY_RECEIPT_MAX_COUNT = "android.retryReceipt.maxCount"; + private static final String RETRY_RECEIPT_MAX_COUNT_RESET_AGE = "android.retryReceipt.maxCountResetAge"; + private static final String PREKEY_FORCE_REFRESH_INTERVAL = "android.prekeyForceRefreshInterval"; /** * We will only store remote values for flags in this set. If you want a flag to be controllable @@ -190,7 +193,10 @@ public final class FeatureFlags { USE_ACTIVE_CALL_MANAGER, GIF_SEARCH, AUDIO_REMUXING, - VIDEO_RECORD_1X_ZOOM + VIDEO_RECORD_1X_ZOOM, + RETRY_RECEIPT_MAX_COUNT, + RETRY_RECEIPT_MAX_COUNT_RESET_AGE, + PREKEY_FORCE_REFRESH_INTERVAL ); @VisibleForTesting @@ -259,7 +265,10 @@ public final class FeatureFlags { CALLING_REACTIONS, NOTIFICATION_THUMBNAIL_BLOCKLIST, CALLING_RAISE_HAND, - VIDEO_RECORD_1X_ZOOM + VIDEO_RECORD_1X_ZOOM, + RETRY_RECEIPT_MAX_COUNT, + RETRY_RECEIPT_MAX_COUNT_RESET_AGE, + PREKEY_FORCE_REFRESH_INTERVAL ); /** @@ -435,6 +444,20 @@ public static long retryRespondMaxAge() { return getLong(RETRY_RESPOND_MAX_AGE, TimeUnit.DAYS.toMillis(14)); } + /** + * The max number of retry receipts sends we allow (within @link{#retryReceiptMaxCountResetAge()}) before we consider the volume too large and stop responding. + */ + public static long retryReceiptMaxCount() { + return getLong(RETRY_RECEIPT_MAX_COUNT, 10); + } + + /** + * If the last retry receipt send was older than this, then we reset the retry receipt sent count. (For use with @link{#retryReceiptMaxCount()}) + */ + public static long retryReceiptMaxCountResetAge() { + return getLong(RETRY_RECEIPT_MAX_COUNT_RESET_AGE, TimeUnit.HOURS.toMillis(3)); + } + /** How long a sender key can live before it needs to be rotated. */ public static long senderKeyMaxAge() { return Math.min(getLong(SENDER_KEY_MAX_AGE, TimeUnit.DAYS.toMillis(14)), TimeUnit.DAYS.toMillis(90)); @@ -670,6 +693,11 @@ public static boolean startVideoRecordAt1x() { return getBoolean(VIDEO_RECORD_1X_ZOOM, false); } + /** How often we allow a forced prekey refresh. */ + public static long preKeyForceRefreshInterval() { + return getLong(PREKEY_FORCE_REFRESH_INTERVAL, TimeUnit.HOURS.toMillis(1)); + } + /** Only for rendering debug info. */ public static synchronized @NonNull Map getMemoryValues() { return new TreeMap<>(REMOTE_VALUES); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/JobExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/util/JobExtensions.kt new file mode 100644 index 0000000000..dde6c12daf --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/util/JobExtensions.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.util + +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.jobmanager.Job +import org.thoughtcrime.securesms.jobmanager.JobManager + +/** Starts a new chain with this job. */ +fun Job.asChain(): JobManager.Chain { + return ApplicationDependencies.getJobManager().startChain(this) +} diff --git a/app/src/main/protowire/JobData.proto b/app/src/main/protowire/JobData.proto index c048c958d6..89823e7a9c 100644 --- a/app/src/main/protowire/JobData.proto +++ b/app/src/main/protowire/JobData.proto @@ -36,4 +36,8 @@ message AttachmentUploadJobData { uint64 attachmentId = 1; reserved /*attachmentUniqueId*/ 2; optional ResumableUpload uploadSpec = 3; +} + +message PreKeysSyncJobData { + bool forceRefreshRequested = 1; } \ No newline at end of file From cdb6c16473bfa8b80ec66b18cd32a722baeddf45 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Fri, 23 Feb 2024 16:25:05 -0500 Subject: [PATCH 16/18] Update translations and other static files. --- app/src/main/res/values-af/strings.xml | 4 ++- app/src/main/res/values-ar/strings.xml | 2 ++ app/src/main/res/values-az/strings.xml | 2 ++ app/src/main/res/values-bg/strings.xml | 2 ++ app/src/main/res/values-bn/strings.xml | 2 ++ app/src/main/res/values-bs/strings.xml | 2 ++ app/src/main/res/values-ca/strings.xml | 2 ++ app/src/main/res/values-cs/strings.xml | 2 ++ app/src/main/res/values-da/strings.xml | 2 ++ app/src/main/res/values-de/strings.xml | 4 ++- app/src/main/res/values-el/strings.xml | 2 ++ app/src/main/res/values-es/strings.xml | 2 ++ app/src/main/res/values-et/strings.xml | 2 ++ app/src/main/res/values-eu/strings.xml | 2 ++ app/src/main/res/values-fa/strings.xml | 24 ++++++++-------- app/src/main/res/values-fi/strings.xml | 2 ++ app/src/main/res/values-fr/strings.xml | 12 ++++---- app/src/main/res/values-ga/strings.xml | 2 ++ app/src/main/res/values-gl/strings.xml | 2 ++ app/src/main/res/values-gu/strings.xml | 4 ++- app/src/main/res/values-hi/strings.xml | 8 ++++-- app/src/main/res/values-hr/strings.xml | 2 ++ app/src/main/res/values-hu/strings.xml | 2 ++ app/src/main/res/values-in/strings.xml | 2 ++ app/src/main/res/values-it/strings.xml | 2 ++ app/src/main/res/values-iw/strings.xml | 2 ++ app/src/main/res/values-ja/strings.xml | 2 ++ app/src/main/res/values-ka/strings.xml | 2 ++ app/src/main/res/values-kk/strings.xml | 2 ++ app/src/main/res/values-km/strings.xml | 2 ++ app/src/main/res/values-kn/strings.xml | 2 ++ app/src/main/res/values-ko/strings.xml | 2 ++ app/src/main/res/values-ky/strings.xml | 2 ++ app/src/main/res/values-lt/strings.xml | 2 ++ app/src/main/res/values-lv/strings.xml | 2 ++ app/src/main/res/values-mk/strings.xml | 2 ++ app/src/main/res/values-ml/strings.xml | 2 ++ app/src/main/res/values-mr/strings.xml | 2 ++ app/src/main/res/values-ms/strings.xml | 2 ++ app/src/main/res/values-my/strings.xml | 2 ++ app/src/main/res/values-nb/strings.xml | 2 ++ app/src/main/res/values-nl/strings.xml | 6 ++-- app/src/main/res/values-pa/strings.xml | 2 ++ app/src/main/res/values-pl/strings.xml | 2 ++ app/src/main/res/values-pt-rBR/strings.xml | 2 ++ app/src/main/res/values-pt/strings.xml | 2 ++ app/src/main/res/values-ro/strings.xml | 2 ++ app/src/main/res/values-ru/strings.xml | 2 ++ app/src/main/res/values-sk/strings.xml | 2 ++ app/src/main/res/values-sl/strings.xml | 2 ++ app/src/main/res/values-sq/strings.xml | 4 ++- app/src/main/res/values-sr/strings.xml | 8 ++++-- app/src/main/res/values-sv/strings.xml | 8 ++++-- app/src/main/res/values-sw/strings.xml | 2 ++ app/src/main/res/values-ta/strings.xml | 32 ++++++++++++---------- app/src/main/res/values-te/strings.xml | 2 ++ app/src/main/res/values-th/strings.xml | 2 ++ app/src/main/res/values-tl/strings.xml | 2 ++ app/src/main/res/values-tr/strings.xml | 2 ++ app/src/main/res/values-ug/strings.xml | 2 ++ app/src/main/res/values-uk/strings.xml | 2 ++ app/src/main/res/values-ur/strings.xml | 2 ++ app/src/main/res/values-vi/strings.xml | 2 ++ app/src/main/res/values-yue/strings.xml | 2 ++ app/src/main/res/values-zh-rCN/strings.xml | 2 ++ app/src/main/res/values-zh-rHK/strings.xml | 2 ++ app/src/main/res/values-zh-rTW/strings.xml | 2 ++ app/static-ips.gradle.kts | 8 +++--- 68 files changed, 184 insertions(+), 50 deletions(-) diff --git a/app/src/main/res/values-af/strings.xml b/app/src/main/res/values-af/strings.xml index 7749a559d5..69d3b59257 100644 --- a/app/src/main/res/values-af/strings.xml +++ b/app/src/main/res/values-af/strings.xml @@ -2295,6 +2295,8 @@ Skrap Gebruikersnaam suksesvol verwyder. Netwerkfout teëgekom. + + Too many attempts made, please try again later. Hierdie gebruikersnaam is reeds in gebruik. Gebruikersname kan slegs a–Z, 0–9 en onderstrepings bevat. Gebruikersname kan nie met \'n syfer begin nie. @@ -3152,7 +3154,7 @@ Donker Voorkoms Tema - Kletskleur & amp; muurpapier + Kletskleur & muurpapier Toepassingikoon diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 90c1cfade4..f1fbbc46ae 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -2615,6 +2615,8 @@ حذف تم حذف اسم المُستخدم بنجاح. حدث خطأ فى الشبكة + + Too many attempts made, please try again later. اسم المُستخدم هذا غير متوفّر. أسماء المُستخدمين يجب أن تحتوي فقط على الحروف الأبجدية، 0-9، أو \"_\". اسم المُستخدم لا يمكن أن يبدأ برقم. diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index c7786f334c..252d68a337 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -2295,6 +2295,8 @@ Sil İstifadəçi adı uğurla çıxarıldı. Şəbəkə xətası ilə qarşılaşıldı. + + Too many attempts made, please try again later. Bu istifadəçi adı götürülüb. İstifadəçi adları yalnız a–Z, 0–9 və altdan xətt ehtiva edə bilər. İstifadəçi adı nömrə ilə başlaya bilməz. diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 97a655a7cc..e6b4ea817a 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -2295,6 +2295,8 @@ Изтриване Успешно премахнато потребителско име. Възникна мрежова грешка. + + Too many attempts made, please try again later. Това потребителско име е взето. Потребителските имена могат да включват само a–Z, 0–9 и долни черти. Потребителските имена не могат да започват с цифра. diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index 1da4baed01..7ab7ccab8f 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -2295,6 +2295,8 @@ মুছে ফেলুন সফলভাবে ব্যবহারকারীর নাম মুছে ফেলা হয়েছে। একটি নেটওয়ার্ক ত্রুটি সম্মুখীন। + + Too many attempts made, please try again later. এই ব্যবহারকারীর নাম ব্যবহৃত হয়েছে। ব্যবহারকারীর নামে থাকতে পারবে শুধুমাত্র a–Z, 0–9, ও আন্ডারস্কোর। ব্যবহারকারীর নাম সংখ্যা দিয়ে শুরু করতে পারবে না। diff --git a/app/src/main/res/values-bs/strings.xml b/app/src/main/res/values-bs/strings.xml index 41f4e305eb..962c49c7c2 100644 --- a/app/src/main/res/values-bs/strings.xml +++ b/app/src/main/res/values-bs/strings.xml @@ -2455,6 +2455,8 @@ Izbriši Uspješno je uklonjeno korisničko ime. Došlo je do greške u mreži. + + Too many attempts made, please try again later. Ovo korisničko ime već je zauzeto. Korisničko ime može sadržavati samo slova bez kvačica, brojeve i donju crtu. Korisničko ime ne može početi brojem. diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 501185dd7c..7611d12a48 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -2295,6 +2295,8 @@ Suprimeix L\'àlies s\'ha eliminat correctament. Hi ha hagut un error de xarxa. + + Too many attempts made, please try again later. Aquest àlies ja està agafat. Els àlies només poden incloure a-z, 0-9 i _. Els àlies no poden començar amb un número. diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 156194fbd1..eef1e87db1 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -2455,6 +2455,8 @@ Odstranit Uživatelské jméno bylo úspěšně odstraněno. Došlo k chybě v síti. + + Too many attempts made, please try again later. Toto uživatelské jméno je obsazené. Uživatelská jména mohou obsahovat pouze a–Z, 0–9 a podtržítka. Uživatelská jména nesmí začínat číslicí. diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 0023a05cf4..517f10fd64 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -2295,6 +2295,8 @@ Slet Brugernavn slettet. Registreret en netværksfejl + + Too many attempts made, please try again later. Brugernavnet er optaget. Brugernavne må kun indeholde a-Z, 0-9 og underscores. Brugernavne kan ikke starte med et tal. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index a3e413b48c..e7b5123c03 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1967,7 +1967,7 @@ Verifiziert - Keine Direktnachrichten mit %1$s + Keine Einzelchats mit %1$s %1$s ist in deinen Telefonkontakten @@ -2295,6 +2295,8 @@ Löschen Nutzername erfolgreich entfernt. Ein Netzfehler ist aufgetreten. + + Too many attempts made, please try again later. Dieser Nutzername ist vergeben. Nutzernamen dürfen nur a–Z, 0–9 und Unterstriche beinhalten. Nutzernamen dürfen nicht mit einer Ziffer beginnen. diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index f09c54b16b..52c920e8d0 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -2295,6 +2295,8 @@ Διαγραφή Το όνομα χρήστη αφαιρέθηκε με επιτυχία. Υπήρξε σφάλμα δικτύου. + + Too many attempts made, please try again later. Αυτό το όνομα χρήστη χρησιμοποιείται ήδη. Τα ονόματα χρήστη μπορούν να περιέχουν μόνο λατινικούς χαρακτήρες a-Z, αριθμούς 0-9 και κάτω παύλες. Τα ονόματα χρήστη δεν μπορούν να αρχίζουν με αριθμό diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index b4c7dc0211..40a9236831 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -2295,6 +2295,8 @@ Eliminar Alias eliminado con éxito. Fallo en la conexión de red. + + Too many attempts made, please try again later. Alias en uso por otra persona. Tu alias solo puede contener a–Z, 0–9 y _. El alias no puede comenzar por un número. diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 0b0267a1c7..876369f727 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -2295,6 +2295,8 @@ Kustuta Kasutajanimi edukalt eemaldatud. Esines võrgutõrge. + + Too many attempts made, please try again later. See kasutajanimi on võetud. Kasutajanimed võivad sisaldada ainult a–Z, 0–9 ja alakriipse. Kasutajanimed ei tohi alata numbriga. diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 7f37fde7e4..f358ed6113 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -2295,6 +2295,8 @@ Ezabatu Kendu da erabiltzaile-izena. Sare errore bat izan da. + + Too many attempts made, please try again later. Erabiltzaile-izen hau hartuta dago. Erabiltzaile-izenetan bakarrik ondokoak daude onartuta: a-Z, 0-9 eta azpimarrak. Erabiltzaile-izenak ezin dira zenbaki batez hasi. diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 6e98798a8c..276205b399 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -19,7 +19,7 @@ بله - خیر + نه پاک کردن لطفاً صبر کنید… ذخیره @@ -186,11 +186,11 @@ به‌عنوان هرزنامه گزارش شود - سیگنال مطلع خواهد شد که این فرد ممکن است در حال ارسال هرزنامه باشد. سیگنال نمی‌تواند محتوای گفتگوها را ببیند. + سیگنال مطلع خواهد شد که این فرد ممکن است در حال ارسال هرزنامه باشد. سیگنال نمی‌تواند محتوای هیچ گفتگویی را ببیند. - سیگنال مطلع خواهد شد که %1$s که شما را به این گروه دعوت کرده ممکن است در حال ارسال هرزنامه باشد. سیگنال نمی‌تواند محتوای گفتگوها را ببیند. + سیگنال مطلع خواهد شد که %1$s که شما را به این گروه دعوت کرده ممکن است در حال ارسال هرزنامه باشد. سیگنال نمی‌تواند محتوای هیچ گفتگویی را ببیند. - سیگنال مطلع خواهد شد که فردی که شما را به این گروه دعوت کرده ممکن است در حال ارسال هرزنامه باشد. سیگنال نمی‌تواند محتوای گفتگوها را ببیند. + سیگنال مطلع خواهد شد که فردی که شما را به این گروه دعوت کرده ممکن است در حال ارسال هرزنامه باشد. سیگنال نمی‌تواند محتوای هیچ گفتگویی را ببیند. امروز @@ -530,7 +530,7 @@ پذیرفتن - حذف گفتگو + پاک کردن گفتگو رفع مسدودیت @@ -551,21 +551,21 @@ این درخواست را بادقت مرور کنید. هیچ‌یک از مخاطبان شما یا کسانی که با آن‌ها گفتگو می‌کنید در این گروه نیستند. چند چیز که باید در مورد آن‌ها احتیاط کنید: - نکته قبلی + نکتۀ قبلی - نکته بعدی + نکتۀ بعدی - هرزنامه‌های پولی یا رمزارز + کلاه‌برداری‌های پولی یا رمزارز If someone you don’t know messages about cryptocurrency (like Bitcoin) or a financial opportunity, be careful—it’s likely spam. پیام‌های مبهم یا نامرتبط - ارسال‌کنندگان هرزنامه معمولاً با پیام ساده‌ای مثل «سلام» شروع می‌کنند تا شما را درگیر کنند. اگر پاسخ دهید ممکن است درگیر ادامه ماجرا شوید. + ارسال‌کنندگان هرزنامه معمولاً با پیام ساده‌ای مثل «سلام» شروع می‌کنند تا شما را درگیر کنند. اگر پاسخ دهید ممکن است گفتگو را بیشتر پیش ببرند تا شما را درگیر کنند. پیام‌های دارای پیوند - در مورد پیام‌هایی که از افراد ناشناس دریافت می‌کنید و داخل آن‌ها پیوند به وب‌سایت‌ها وجود دارد احتیاط کنید. هیچ‌وقت پیوندی که از افراد غیرقابل‌اعتماد دریافت کرده‌اید را باز نکنید. + در مورد پیام‌هایی که از افراد ناشناس دریافت می‌کنید و داخل آن‌ها پیوند به وب‌سایتی وجود دارد احتیاط کنید. هیچ‌وقت پیوندی که از افراد غیرقابل‌اعتماد دریافت کرده‌اید را باز نکنید. کسب‌وکارها و سازمان‌های جعلی @@ -2295,6 +2295,8 @@ پاک کردن نام کاربری با موفقیت حذف شد. خطای شبکه رخ داد. + + Too many attempts made, please try again later. این نام کاربری گرفته شده است. نام‌های کاربری فقط می‌توانند حاوی حروف A تا Z، اعداد ۰ تا ۹ و _ باشند. نام‌های کاربری نمی‌توانند با عدد آغاز شوند. @@ -6565,7 +6567,7 @@ شماره تلفن - نام کاربری و سپس نقطه و مجموعه اعداد آن را وارد کنید. + یک نام کاربری و سپس یک نقطه و مجموعۀ اعداد آن را وارد کنید. بعدی diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index ef3e0648bd..68a4af9248 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -2295,6 +2295,8 @@ Poista Käyttäjänimen poisto onnistui. Verkkovirhe. + + Too many attempts made, please try again later. Tämä käyttäjänimi on varattu. Käyttäjänimet voivat sisältää vain merkkejä a–z, 0–9 ja alaviivoja. Käyttäjänimi ei voi alkaa numerolla. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index f06b17c27e..34d36414b7 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1601,7 +1601,7 @@ Signalé comme spam - Vous avez accepté la demande de message + Vous avez accepté l’invitation par message Accepter @@ -2207,7 +2207,7 @@ Info sur l’appareil : Version d’Android : Version de Signal : - Paquet Signal : + Package Signal : Blocage de l’inscription : Paramètres régionaux : @@ -2295,6 +2295,8 @@ Supprimer Le nom d’utilisateur a été effacé avec succès. Une erreur réseau est survenue. + + Too many attempts made, please try again later. Ce nom d’utilisateur existe déjà. Les noms d’utilisateur ne peuvent inclure que des caractères de A à Z, 0 à 9 et des tirets bas. Un nom d’utilisateur ne peut pas commencer par un chiffre. @@ -2588,7 +2590,7 @@ - Sortie son + Sortie audio Écouteur de téléphone @@ -2677,8 +2679,8 @@ Afficher/masquer le clavier des émojis Imagette de fichiers joints Afficher/masquer le tiroir de l’appareil photo à basse résolution - Enregistrer et envoyer une fichier son joint - Verrouiller l’enregistrement de fichiers son joints + Enregistrer et envoyer un message vocal en pièce jointe + Verrouiller l’enregistrement de pièces jointes au format audio Le message n’a pas pu être envoyé. Veuillez vérifier votre connexion et réessayer. diff --git a/app/src/main/res/values-ga/strings.xml b/app/src/main/res/values-ga/strings.xml index b5656ea145..b36b3c76ca 100644 --- a/app/src/main/res/values-ga/strings.xml +++ b/app/src/main/res/values-ga/strings.xml @@ -2535,6 +2535,8 @@ Scrios D\'éirigh le hainm úsáideora a bhaint. Tharla earráid líonra + + Too many attempts made, please try again later. Tá an t-ainm úsáideora sin gafa cheana. Ní cheadaítear ach a-Z, 0-9, agus fostríoca in ainmneacha úsáideora. Ní cheadaítear uimhir ag tús ainm úsáideora. diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index abc25663c9..91b962d56d 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -2295,6 +2295,8 @@ Eliminar Nome de usuario eliminado satisfactoriamente. Houbo un fallo na rede. + + Too many attempts made, please try again later. Este nome de usuario xa está en uso. Os nomes de usuario só poden incluír a-Z, 0-9 e guións baixos. Os nomes de usuario non poden comezar por un número. diff --git a/app/src/main/res/values-gu/strings.xml b/app/src/main/res/values-gu/strings.xml index 7aefad30d3..080c35cfd6 100644 --- a/app/src/main/res/values-gu/strings.xml +++ b/app/src/main/res/values-gu/strings.xml @@ -2295,6 +2295,8 @@ ડિલીટ કરો યુઝરનેમ સફળતાપૂર્વક દૂર કર્યું. નેટવર્ક ભૂલ મળી. + + Too many attempts made, please try again later. આ યુઝરનેમ લેવાઈ ગયું છે. યુઝરનેમમાં માત્ર a–Z, 0–9 અને અન્ડરસ્કોર શામેલ હોઈ શકે છે. યુઝરનેમ સંખ્યા સાથે શરૂ થઈ શકતા નથી. @@ -3152,7 +3154,7 @@ ડાર્ક દેખાવ થીમ - ચેટ કલર & amp; વૉલપેપર + ચેટ કલર & વૉલપેપર ઍપ આઇકન diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 4100a5ca52..50a3836485 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -536,7 +536,7 @@ रिपोर्ट किया गया स्पैम - Signal has been notified that this person may be sending spam. Signal can’t see the content of any chats. + Signal को सूचित किया गया है कि यह व्यक्ति स्पैम भेज सकता है। Signal किसी भी चैट की सामग्री नहीं देख सकता। स्पैम के रूप में रिपोर्ट किया गया @@ -557,7 +557,7 @@ क्रिप्टो या मनी स्कैम - If someone you don’t know messages about cryptocurrency (like Bitcoin) or a financial opportunity, be careful—it’s likely spam. + यदि कोई व्यक्ति जिसे आप नहीं जानते हैं, क्रिप्टोकरेंसी (जैसे बिटकॉइन) या वित्तीय अवसर के बारे में संदेश भेजता है, तो सावधान रहें - यह संभावित रूप से स्पैम है। अस्पष्ट या अप्रासंगिक संदेश @@ -2295,6 +2295,8 @@ डिलीट करें सफलतापूर्वक यूज़रनेम हटा दिया गया। एक नेटवर्क त्रुटि का सामना करना पड़ा| + + Too many attempts made, please try again later. यह यूज़रनेम ले लिया गया है। यूज़रनेम में केवल a–Z, 0–9, और अंडरस्कोर शामिल हो सकते हैं। यूज़रनेम एक संख्या से शुरू नहीं हो सकते हैं। @@ -2318,7 +2320,7 @@ यह संख्या 00 नहीं हो सकती। 1-9 के बीच का कोई अंक दर्ज करें - Numbers with more than 2 digits can\'t start with 0 + 2 से अधिक अंकों वाली संखाएं 0 से शुरू नहीं हो सकतीं आपके यूज़रनेम को रिकवर करने से आपका मौजूदा QR कोड और लिंक फिर से स्थापित हो जाएगा। क्या आपको यकीन है? diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index cd9da226a1..16e80916cb 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -2455,6 +2455,8 @@ Izbriši Korisničko ime uspješno je uklonjeno. Došlo je do mrežne pogreške. + + Too many attempts made, please try again later. Korisničko ime je zauzeto. Korisnička imena smiju sadržavati samo a-Z, 0-9 i podvlake. Korisnička imena ne mogu započinjati s brojem. diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 8f4c3e9f8d..99e5b3117a 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -2295,6 +2295,8 @@ Törlés A felhasználónév eltávolítása sikeres volt. Hálózati hiba történt. + + Too many attempts made, please try again later. A felhasználónév foglalt. A felhasználónevek csak a-Z, 0-9 és alsóvonás karaktereket tartalmazhatnak. A felhasználónevek nem kezdődhetnek számmal. diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 9b9932947d..e5bcb2ffbf 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -2215,6 +2215,8 @@ Hapus Nama pengguna berhasil dihapus. Terjadi galat jaringan. + + Too many attempts made, please try again later. Nama pengguna telah digunakan. Nama pengguna hanya boleh berisi a–Z, 0–9, dan garisbawah. Nama pengguna tidak boleh dimulai dengan angka. diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 4de875d7b0..e599bde942 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -2295,6 +2295,8 @@ Elimina Nome utente rimosso con successo. Si è verificato un errore di rete. + + Too many attempts made, please try again later. Questo nome utente è già in uso. I nomi utente possono includere solo a–Z, 0–9 e trattini bassi. I nomi utente non possono iniziare con un numero. diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index 2a40b74b41..0b20978bb1 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -2455,6 +2455,8 @@ מחיקה שם משתמש הוסר בהצלחה. היישום נתקל בשגיאת רשת. + + Too many attempts made, please try again later. שם משתמש זה תפוס. שמות משתמש יכולים להכיל רק a–Z, 0–9, וקווים תחתונים. שמות משתמש אינם יכולים להתחיל במספר. diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 35ae5aa0d1..cdc7e628ff 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -2215,6 +2215,8 @@ 消去する ユーザーネームを削除しました。 ネットワークエラーが発生しました。 + + Too many attempts made, please try again later. このユーザーネームは既に使用されています。 ユーザーネームには半角の英数字とアンダーバーのみ使用できます。 ユーザーネームの先頭に数字は使用できません。 diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml index 14fdd37869..9c8b2a21bc 100644 --- a/app/src/main/res/values-ka/strings.xml +++ b/app/src/main/res/values-ka/strings.xml @@ -2295,6 +2295,8 @@ წაშლა მომხმარებლის სახელი წარმატებით წაიშალა. ქსელის ხარვეზს გადავაწყდით. + + Too many attempts made, please try again later. ეს მომხმარებლის სახელი დაკავებულია. მომხმარებლის სახელი შეიძლება შეიცავდეს მხოლოდ a–Z, 0–9 და ქვედა ტირეს. მომხმარებლის სახელი ციფრით ვერ დაიწყება. diff --git a/app/src/main/res/values-kk/strings.xml b/app/src/main/res/values-kk/strings.xml index ab31de8908..1d7f7e708a 100644 --- a/app/src/main/res/values-kk/strings.xml +++ b/app/src/main/res/values-kk/strings.xml @@ -2295,6 +2295,8 @@ Жою Пайдаланушы аты өшірілді. Желі қатесі анықталды. + + Too many attempts made, please try again later. Бұл пайдаланушы аты бос емес. Пайдаланушы аттарында a–Z, 0–9 және астыңғы сызықтар бола алады. Пайдаланушы аттары саннан басталмауы керек. diff --git a/app/src/main/res/values-km/strings.xml b/app/src/main/res/values-km/strings.xml index 6137e1a814..4b1e64d552 100644 --- a/app/src/main/res/values-km/strings.xml +++ b/app/src/main/res/values-km/strings.xml @@ -2215,6 +2215,8 @@ លុប លុបឈ្មោះអ្នកប្រើបានជោគជ័យ។ បានជួបប្រទះកំហុសបណ្តាញ។ + + Too many attempts made, please try again later. ឈ្មោះអ្នកប្រើនេះត្រូវបានយកហើយ។ ឈ្មោះអ្នកប្រើអាចរួមបញ្ចូលតែ a–Z, 0–9, និងបន្ទាត់ពីក្រោមប៉ុណ្ណោះ។ ឈ្មោះអ្នកប្រើមិនអាចចាប់ផ្តើមដោយលេខបានទេ។ diff --git a/app/src/main/res/values-kn/strings.xml b/app/src/main/res/values-kn/strings.xml index 8974511dfa..86575a3156 100644 --- a/app/src/main/res/values-kn/strings.xml +++ b/app/src/main/res/values-kn/strings.xml @@ -2295,6 +2295,8 @@ ಅಳಿಸಿ ಬಳಕೆದಾರ ಹೆಸರನ್ನು ಯಶಸ್ವಿಯಾಗಿ ತೆಗೆದುಹಾಕಲಾಗಿದೆ. ನೆಟ್‌ವರ್ಕ್ ದೋಷವನ್ನು ಎದುರಿಸಿದೆ. + + Too many attempts made, please try again later. ಈ ಯೂಸರ್ ನೇಮ್ ತೆಗೆದುಕೊಳ್ಳಲಾಗಿದೆ. ಯೂಸರ್ ನೇಮ್ ಗಳು a-Z, 0-9, ಮತ್ತು ಅಡಿಗೆರೆಗಳನ್ನು ಮಾತ್ರ ಒಳಗೊಂಡಿರಬಹುದು. ಯೂಸರ್ ನೇಮ್ ಗಳು ಸಂಖ್ಯೆಯಿಂದ ಪ್ರಾರಂಭವಾಗುವುದಿಲ್ಲ. diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index fb35a4dced..0532ba1bec 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -2215,6 +2215,8 @@ 삭제 사용자 이름이 성공적으로 삭제되었습니다. 네트워크 오류가 발생했습니다. + + Too many attempts made, please try again later. 사용 중인 사용자 이름입니다. 사용자 이름에는 a~Z, 0~9 및 밑줄만 포함할 수 있습니다. 사용자 이름은 숫자로 시작할 수 없습니다. diff --git a/app/src/main/res/values-ky/strings.xml b/app/src/main/res/values-ky/strings.xml index b091673085..81ae7f8be9 100644 --- a/app/src/main/res/values-ky/strings.xml +++ b/app/src/main/res/values-ky/strings.xml @@ -2215,6 +2215,8 @@ Өчүрүү Колдонуучу аты өчүрүлдү. Тармакта ката кетти. + + Too many attempts made, please try again later. Мындай ат бар. Колдонуучунун аты a-Z, 0-9 деген тамгалар менен сандардан жана ылдыйкы сызыктан турат. Колдонуучунун аты сандан башталбайт. diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 9d6e2fa637..3d48229ef3 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -2455,6 +2455,8 @@ Ištrinti Naudotojo vardas sėkmingai pašalintas. Susidurta su tinklo klaida. + + Too many attempts made, please try again later. Šis naudotojo vardas užimtas. Naudotojo varduose gali būti tik simboliai a–Z, 0–9 ir pabraukimo brūkšniai. Naudotojo vardai negali prasidėti skaitmenimi. diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index 004d0cce01..a62ab3149c 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -2375,6 +2375,8 @@ Dzēst Lietotājvārds ir sekmīgi noņemts. Tīkla kļūda. + + Too many attempts made, please try again later. Šis lietotājvārds ir aizņemts. Lietotājvārdi var ietvert tikai a–Z, 0–9 un pasvītrojuma zīmes. Lietotājvārdi nevar sākties ar skaitli. diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index cd5289b3c9..1673f11282 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -2295,6 +2295,8 @@ Избриши Успешно отстрането корисничко име. Се случи мрежна грешка. + + Too many attempts made, please try again later. Корисничкото име е веќе во употреба. Корисничките имиња можат да се состојат од а-Ш, 0-9 и долни цртички. Корисничкото име не може да започне со број. diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index eacf3d8d8c..48d24ef6ee 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -2295,6 +2295,8 @@ ഇല്ലാതാക്കൂ ഉപയോക്തൃനാമം നീക്കം ചെയ്തു. ഒരു നെറ്റ്‌വർക്ക് പിശക് നേരിട്ടു. + + Too many attempts made, please try again later. ഈ ഉപയോക്തൃനാമം എടുത്തിട്ടുണ്ട്. ഉപയോക്തൃനാമങ്ങളിൽ a–Z, 0–9, അണ്ടർ സ്കോറുകൾ എന്നിവ മാത്രമേ ഉൾപ്പെടുത്താനാകൂ. ഉപയോക്തൃനാമം ഒരു നമ്പറിൽ ആരംഭിക്കാൻ കഴിയില്ല. diff --git a/app/src/main/res/values-mr/strings.xml b/app/src/main/res/values-mr/strings.xml index efeedb36e7..bb572caf38 100644 --- a/app/src/main/res/values-mr/strings.xml +++ b/app/src/main/res/values-mr/strings.xml @@ -2295,6 +2295,8 @@ हटवा वापरकर्तानाव यशस्वीरीत्या काढून टाकले. एक नेटवर्क त्रुटी आढळली. + + Too many attempts made, please try again later. हे वापरकर्तानाव घेतलेले आहे. वापरकर्तानावांमध्ये फक्त a–Z, 0–9, आणि अंडरस्कोर याचा समावेश असू शकतो. वापरकर्तानाव अंकांसोबत सोबत चालू होऊ शकत नाही. diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml index cacc94c79f..4c725477c8 100644 --- a/app/src/main/res/values-ms/strings.xml +++ b/app/src/main/res/values-ms/strings.xml @@ -2215,6 +2215,8 @@ Padam Berjaya mengalih keluar nama pengguna. Menghadapi ralat rangkaian. + + Too many attempts made, please try again later. Nama pengguna ini telah diambil. Nama pengguna hanya boleh mengandungi a–Z, 0–9 dan garis bawah. Nama pengguna tidak boleh bermula dengan nombor. diff --git a/app/src/main/res/values-my/strings.xml b/app/src/main/res/values-my/strings.xml index abfa757244..649fc4714c 100644 --- a/app/src/main/res/values-my/strings.xml +++ b/app/src/main/res/values-my/strings.xml @@ -2215,6 +2215,8 @@ ဖျက်ရန် အသုံးပြုသူအမည်ကို အောင်မြင်စွာ ဖယ်ရှားခဲ့ပြီး။ ကွန်ယက်အမှားတစ်ခုနှင့် ကြုံတွေ့ခဲ့ရသည်။ + + Too many attempts made, please try again later. ဤသုံးစွဲသူအမည်ကို ယူထားပြီးပါပြီ။ သုံးစွဲသူအမည်များတွင် A–Z၊ 0-9 နှင့် \"_\" သင်္ကေတများသာ ပါဝင်နိုင်ပါသည်။ သုံးစွဲသူအမည်များသည် နံပါတ်နှင့် စတင်၍ မရပါ။ diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index a0e724518e..dbc6609d50 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -2295,6 +2295,8 @@ Slett Brukernavnet er fjernet. Det oppstod en nettverksfeil. + + Too many attempts made, please try again later. Dette brukernavnet er tatt. Brukernavn kan bare inneholde a–Z, 0–9 og understrek. Brukernavn kan ikke begynne med et tall. diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index e357f2e030..3d7a20d893 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -1698,7 +1698,7 @@ Kaart Aanwijzer - Adres accepteren + Adres bevestigen De versie van Google Play Services die je in gebruik hebt werkt niet zoals het hoort. Installeer Google Play Services opnieuw en probeer het opnieuw. @@ -1967,7 +1967,7 @@ Geverifieerd - Geen directe berichten met %1$s + Geen individuele chat met %1$s %1$s staat in je systeemcontactenlijst @@ -2295,6 +2295,8 @@ Verwijderen Gebruikersnaam succesvol verwijderd. Er is een netwerkfout opgetreden. + + Too many attempts made, please try again later. Deze gebruikersnaam is al in gebruik. Gebruikersnamen mogen alleen uit a-z, 0-9 en _ bestaan. Gebruikersnamen mogen niet met een cijfer beginnen. diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index 374c7301be..e3859536f5 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -2295,6 +2295,8 @@ ਮਿਟਾਓ ਵਰਤੋਂਕਾਰ-ਨਾਂ ਸਫਲਤਾਪੂਰਵਕ ਹਟਾਇਆ ਗਿਆ। ਨੈੱਟਵਰਕ ਗਲਤੀ ਆਈ ਹੈ। + + Too many attempts made, please try again later. ਇਹ ਵਰਤੋਂਕਾਰ ਨਾਂ ਕੋਈ ਹੋਰ ਵਰਤ ਰਿਹਾ ਹੈ। ਵਰਤੋਂਕਾਰ ਨਾਂ ਵਿੱਚ ਸਿਰਫ਼ a–Z, 0–9 ਅਤੇ ਅੰਡਰਸਕੋਰ ਸ਼ਾਮਲ ਹੋ ਸਕਦੇ ਹਨ। ਵਰਤੋਂਕਾਰ ਨਾਂ ਅੰਕ ਨਾਲ ਸ਼ੁਰੂ ਨਹੀਂ ਹੋ ਸਕਦਾ ਹੈ। diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 5551604538..1559688766 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -2455,6 +2455,8 @@ Usuń Pomyślnie usunięto nazwę użytkownika. Wystąpił błąd sieci. + + Too many attempts made, please try again later. Nazwa użytkownika jest zajęta. Nazwy użytkowników mogą zawierać wyłącznie a–Z, 0–9 i podkreślenia (_). Nazwy użytkowników nie mogą zaczynać się od cyfry. diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 3e0cffa7f1..e2ac4ef29f 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -2295,6 +2295,8 @@ Apagar Usuário excluído com sucesso Foi encontrado um erro de rede. + + Too many attempts made, please try again later. Este nome de usuário já está sendo utilizado. Os nomes de usuário só podem incluir a–Z, 0–9, e sublinhado. Os nomes de usuários não podem começar com um número. diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 423db3b2f2..879645f562 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -2295,6 +2295,8 @@ Eliminar Nome de utilizador removido com sucesso. Encontrado um erro de rede. + + Too many attempts made, please try again later. Este nome de utilizador já se encontra em utilização. Os nomes de utilizadores apenas podem incluir a-Z, 0-9 e underscores. Os nomes de utilizadores não podem começar com um número. diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 5dd845989d..dbd72fa141 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -2375,6 +2375,8 @@ Șterge Numele de utilizator a fost eliminat cu succes. A apărut o eroare de rețea. + + Too many attempts made, please try again later. Acest nume de utilizator este luat. Numele de utilizatori pot conține doar a-Z, 0-9, și _. Numele de utilizator nu pot începe cu un număr. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index c4b8cb6161..895d9c1fa7 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -2455,6 +2455,8 @@ Удалить Имя пользователя успешно удалено. Обнаружена ошибка сети. + + Too many attempts made, please try again later. Это имя пользователя занято. Имена пользователей могут содержать только a–Z, 0–9 и нижние подчёркивания. Имена пользователей не могут начинаться с цифр. diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 1951c49fd8..ffa30f1159 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -2455,6 +2455,8 @@ Vymazať Používateľské meno bolo úspešne odstránené. Vyskytla sa chyba siete. + + Too many attempts made, please try again later. Toto používateľské meno je obsadené. Používateľské mená môžu obsahovať iba znaky a-Z, 0-9 a podčiarkovníky. Používateľské mená nemôžu začínať číslicou. diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index d80a01c1d9..04ea5beb65 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -2455,6 +2455,8 @@ Izbriši Uspešno ste izbrisali uporabniško ime. Prišlo je do napake na omrežju. + + Too many attempts made, please try again later. Uporabniško ime je že zasedeno. Uporabniška imena lahko vsebujejo samo znake a-Z, 0-9 in podčrtaje. Uporabniška imena se ne smejo začeti s števikami. diff --git a/app/src/main/res/values-sq/strings.xml b/app/src/main/res/values-sq/strings.xml index 59eba20570..ec660c1eff 100644 --- a/app/src/main/res/values-sq/strings.xml +++ b/app/src/main/res/values-sq/strings.xml @@ -2295,6 +2295,8 @@ Fshije Emri i përdoruesit u hoq me sukses. U has një gabim rrjeti. + + Too many attempts made, please try again later. Ky emër përdoruesi është i zënë. Emrat e përdoruesve mund të përmbajnë vetëm a-Z, 0-9 dhe nënvija. Emrat e përdoruesve nuk mund të fillojnë me numër. @@ -3150,7 +3152,7 @@ Pajisje të lidhura E çelët E errët - Dukje + Paraqitja Temë Ngjyra e sfondit të bisedës & letra e murit diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index adc0a840dd..91370472dd 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -536,7 +536,7 @@ Спам је пријављен - Signal has been notified that this person may be sending spam. Signal can’t see the content of any chats. + Signal је обавештен да ова особа можда шаље спам. Signal не може да види садржај ниједног ћаскања. Пријављено је као спам @@ -557,7 +557,7 @@ Крипто или новчане преваре - If someone you don’t know messages about cryptocurrency (like Bitcoin) or a financial opportunity, be careful—it’s likely spam. + Ако вам неко кога не познајете шаље поруке о криптовалути (као што је Bitcoin) или некој финансијској прилици, будите опрезни – вероватно је у питању спам. Нејасне или неповезане поруке @@ -2295,6 +2295,8 @@ Обриши Корисничко име уклоњено. Дошло је до грешке мреже. + + Too many attempts made, please try again later. Ово корисничко име је заузето. Корисничко име може да садржи a-Z, 0-9 и _ Корисничко име не може започети бројем. @@ -2318,7 +2320,7 @@ Овај број не може бити 00. Унесите цифру од 1 до 9 - Numbers with more than 2 digits can\'t start with 0 + Бројеви са више од 2 цифре не могу да почињу са 0 Ако вратите корисничко име, ресетоваће вам се постојећи QR код и линк. Да ли сте сигурни? diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 5ed1802674..ea1d8ed044 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -536,7 +536,7 @@ Rapporterade skräppost - Signal has been notified that this person may be sending spam. Signal can’t see the content of any chats. + Signal har meddelats att denna person kanske skickar skräppost. Signal kan inte se innehållet i några chattar. Rapporterad som skräppost @@ -557,7 +557,7 @@ Krypto- eller penningbedrägerier - If someone you don’t know messages about cryptocurrency (like Bitcoin) or a financial opportunity, be careful—it’s likely spam. + Om någon du inte känner skickar meddelanden om kryptovalutor (som Bitcoin) eller en finansiell möjlighet ska du vara försiktig – det är sannolikt skräppost. Vaga eller irrelevanta meddelanden @@ -2295,6 +2295,8 @@ Ta bort Användarnamn borttaget. Stött på ett nätverksfel. + + Too many attempts made, please try again later. Detta användarnamn är taget. Användarnamn kan bara innehålla a–Z, 0–9 och understreck. Användarnamn kan inte börja med en siffra. @@ -2318,7 +2320,7 @@ Det här numret kan inte vara 00. Ange en siffra mellan 1–9 - Numbers with more than 2 digits can\'t start with 0 + Nummer med fler än 2 siffror kan inte börja med 0 Om du återställer ditt användarnamn återställs din befintliga QR-kod och länk. Är du säker? diff --git a/app/src/main/res/values-sw/strings.xml b/app/src/main/res/values-sw/strings.xml index be9aedb963..64477ab800 100644 --- a/app/src/main/res/values-sw/strings.xml +++ b/app/src/main/res/values-sw/strings.xml @@ -2295,6 +2295,8 @@ Futa Jina la mtumiaji limeondolewa kwa mafanikio. Imekumbana na hitilafu ya mtandao. + + Too many attempts made, please try again later. Jina hili la mtumiaji limechukuliwa. Majina ya watumiaji yanaweza kujumuisha tu herufi za A hadi Z, nambari za 0 hadi 9, na vistari-chini. Majina ya watumiaji hayawezi kuanza kwa nambari. diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index 1a382f627b..5fde6a01a1 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -182,15 +182,15 @@ புகாரளித்து தடு - ஸ்பேம் எனப் புகாரளிக்கவா? + ஸ்பேம் எனப் புகாரளிப்பதா? ஸ்பேம் எனப் புகாரளி - இந்த நபர் உங்களுக்கு ஸ்பேம் அனுப்பியிருக்கலாம் என்பதை சிக்னல் உங்களுக்குத் தெரிவிக்கிறது. சாட்களின் உள்ளடக்கம் எதையும் சிக்னலால் பார்க்க முடியவில்லை. + இந்த நபர் உங்களுக்கு ஸ்பேம் அனுப்புவதாக சிக்னலுக்குத் தெரிவிக்கப்படும். எந்தவொரு சாட்களின் உள்ளடக்கத்தையும் சிக்னலால் பார்க்க முடியாது. - உங்களை இந்தக் குழுவிற்கு அழைத்த %1$s, ஸ்பேமை அனுப்பியிருக்கலாம் என்பதை சிக்னல் உங்களுக்குத் தெரிவிக்கிறது. சாட்களின் உள்ளடக்கம் எதையும் சிக்னலால் பார்க்க முடியவில்லை. + உங்களை இந்தக் குழுவிற்கு அழைத்த %1$s, ஸ்பேமை அனுப்பியிருக்கலாம் என்பதை சிக்னலுக்குத் தெரிவிக்கப்படும். எந்தவொரு சாட்களின் உள்ளடக்கத்தையும் சிக்னலால் பார்க்க முடியாது. - உங்களை இந்தக் குழுவிற்கு அழைத்த நபர், ஸ்பேமை அனுப்பியிருக்கலாம் என்பதை சிக்னல் உங்களுக்குத் தெரிவிக்கிறது. சாட்களின் உள்ளடக்கம் எதையும் சிக்னலால் பார்க்க முடியவில்லை. + உங்களை இந்தக் குழுவிற்கு அழைத்த நபர், ஸ்பேமை அனுப்பியிருக்கலாம் என்பதை சிக்னலுக்குத் தெரிவிக்கப்படும். எந்தவொரு சாட்களின் உள்ளடக்கத்தையும் சிக்னலால் பார்க்க முடியாது. இன்று @@ -495,7 +495,7 @@ %1$dகுழு உறுப்பினர்களுக்கு ஒரே பெயர் உண்டு. மதிப்பாய்வு செய்ய தட்டவும் - இந்த நபரின் பெயரில் உங்களிடம் இன்னொரு தொடர்பு உள்ளது + இந்த நபருக்கு மற்றொரு தொடர்பின் அதே பெயர் உள்ளது எங்களை தொடர்பு கொள்ள சரிபார்க்கவும் இப்போது இல்லை @@ -526,13 +526,13 @@ ஸ்பேம் எனப் புகாரளி - தடைசெய் + தடை செய் ஒப்புக்கொள் சாட்டை அழி - தடைநீக்கு + தடை நீக்கு ஸ்பேம் எனப் புகாரளிக்கப்பட்டது @@ -542,12 +542,12 @@ ஸ்பேம் எனப் புகாரளிக்கப்பட்டு தடுக்கப்பட்டது - %1$sஇடமிருந்து பெற்ற மெசேஜ் கோரிக்கையை நீங்கள் ஏற்றுக்கொண்டீர்கள். இது தவறுதலாக நடைபெற்றிருந்தால், கீழே கொடுக்கப்பட்ட நடவடிக்கையிலிருந்து நீங்கள் தேர்ந்தெடுக்கலாம். + %1$sஇடமிருந்து பெற்ற மெசேஜ் கோரிக்கையை நீங்கள் ஏற்றுக்கொண்டீர்கள். இது தவறுதலாக நடைபெற்றிருந்தால், கீழே கொடுக்கப்பட்ட நடவடிக்கையிலிருந்து நீங்கள் ஒன்றைத் தேர்ந்தெடுக்கலாம். பாதுகாப்பு உதவிக்குறிப்புகள் - உங்களுக்கு பரீட்சயமற்றவரிடமிருந்து பெறப்படும் மெசேஜ் கோரிக்கைகளை ஏற்கும்போது கவனமாக இருங்கள். கவனமாக இருங்கள்: + உங்களுக்கு தெரியாத நபர்களிடமிருந்து பெறப்படும் மெசேஜ் கோரிக்கைகளை ஏற்கும்போது கவனமாக இருங்கள். கவனமாக இருங்கள்: கோரிக்கையை கவனமாக மதிப்பாய்வு செய்க. உங்கள் தொடர்புகள் அல்லது நீங்கள் சாட் செய்பவர்கள் யாரும் இந்தக் குழுவில் இல்லை. நீங்கள் கவனிக்க வேண்டிய சில விஷயங்கள் இங்கே கொடுக்கப்பட்டுள்ளன: @@ -561,11 +561,11 @@ தெளிவற்ற அல்லது பொருத்தமற்ற மெசேஜ்கள் - ஸ்பேமர்கள் உங்கள் கவனத்தை ஈர்க்க \"ஹாய்\" போன்ற வழக்கமான மெசேஜ் உடன் தொடங்குவார்கள். அதற்கு நீங்கள் பதிலளித்தால் அவர்கள் உங்களை மேற்கொண்டு ஈடுபடுத்தலாம். + ஸ்பேமர்கள் உங்கள் கவனத்தை ஈர்க்க \"ஹாய்\" போன்ற வழக்கமான மெசேஜ் உடன் தொடங்குவார்கள். அதற்கு நீங்கள் பதிலளித்தால் அவர்கள் உங்களை மேற்கொண்டு உரையாடலைத் தொடரலாம். இணைப்புகளுடன் கூடிய மெசேஜ்கள் - உங்களுக்குப் பரீட்சயமற்ற நபர்களிடமிருந்து இணையதளங்களுக்கான இணைப்புகளுடன் கூடிய மெசேஜ்களைப் பெறும்போது கவனமாக இருங்கள். உங்களுக்கு நம்பிக்கையற்றவரிடமிருந்து இணைப்புகளைப் பெறும்போது ஒருபோதும் பார்வையிடாதீர்கள். + உங்களுக்கு தெரியாத நபர்களிடமிருந்து இணையதளங்களுக்கான இணைப்புகளுடன் கூடிய மெசேஜ்களைப் பெறும்போது கவனமாக இருங்கள். உங்களுக்கு நம்பிக்கையற்றவர்கள் அனுப்பும் இணைப்புகளை ஒருபோதும் பார்வையிடாதீர்கள். போலி நிறுவனங்கள் மற்றும் அமைப்புகள் @@ -1967,7 +1967,7 @@ சரிபார்க்கப்பட்டது - உங்களுக்கும் %1$sக்கும் இடையில் எந்த மெசேஜ்களும் இல்லை + உங்களுக்கும் %1$sக்கும் இடையில் எந்த நேரடி மெசேஜ்களும் இல்லை உங்கள் சிஸ்டம் தொடர்புகளில் %1$s உள்ளார் @@ -2295,6 +2295,8 @@ நீக்கு பயனர் பெயர் வெற்றிகரமாக அகற்றப்பட்டது. பிணைய பிழையை எதிர்கொண்டது. + + Too many attempts made, please try again later. இந்த பயனர் பெயர் எடுக்கப்பட்டது. பயனர் பெயர்களில் அ - ஃ, a–Z, 0–9 மற்றும் அடிக்கோடிட்டுக் காட்டலாம். பயனர் பெயர் எண்ணுடன் தொடங்க முடியாது. @@ -4125,9 +4127,9 @@ - மறுஆய்வு கோரிக்கை + மதிப்பாய்வு கோரிக்கை - மறுஆய்வு கோரிக்கை + மதிப்பாய்வு கோரிக்கை %1$dகுழு உறுப்பினர்களுக்கு ஒரே பெயர் உள்ளது, கீழே உள்ள உறுப்பினர்களை மதிப்பாய்வு செய்து நடவடிக்கை எடுக்க முடிவு செய்யுங்கள் உங்களுக்கு யார் கோரிக்கையை அனுப்பினார்கள் என்பது உங்களுக்குத் தெரியாவிட்டால், கீழே உள்ள தொடர்புகளை மதிப்பாய்வு செய்து நடவடிக்கை எடுக்கவும். பொதுவான குழுக்கள் இல்லை @@ -6565,7 +6567,7 @@ தொலைபேசி எண் - புள்ளி மற்றும் அதன் எண்களின் தொகுப்பைத் தொடர்ந்து பயனர்பெயரை உள்ளிடவும். + ஒரு புள்ளி மற்றும் அதன் எண்களின் தொகுப்பைத் தொடர்ந்து பயனர் பெயரை உள்ளிடவும். அடுத்து diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml index d09db97571..6107bb0b56 100644 --- a/app/src/main/res/values-te/strings.xml +++ b/app/src/main/res/values-te/strings.xml @@ -2295,6 +2295,8 @@ తొలగించండి యూజర్‌నేమ్‌ను విజయవంతంగా తొలగించాము. నెట్‌వర్క్ లోపాన్ని ఎదుర్కొంది. + + Too many attempts made, please try again later. ఈ వినియోగదారు పేరు తీసుకోబడింది. వినియోగదారు పేర్లు a-Z, 0-9 మరియు అండర్ స్కోర్‌లను మాత్రమే కలిగి ఉంటాయి. వినియోగదారు పేర్లు సంఖ్యతో ప్రారంభం కావు. diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index c26cca9464..dbb8102f52 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -2215,6 +2215,8 @@ ลบ ลบชื่อผู้ใช้สำเร็จแล้ว พบความผิดพลาดของเครือข่าย + + Too many attempts made, please try again later. มีผู้ใช้ชื่อนี้แล้ว ชื่อผู้ใช้จะมีได้เฉพาะ a-Z, 0-9 และ _ ชื่อผู้ใช้ไม่สามารถเริ่มต้นด้วยตัวเลข diff --git a/app/src/main/res/values-tl/strings.xml b/app/src/main/res/values-tl/strings.xml index 0a1c662f4f..4a5ba793aa 100644 --- a/app/src/main/res/values-tl/strings.xml +++ b/app/src/main/res/values-tl/strings.xml @@ -2295,6 +2295,8 @@ Burahin Matagumpay na natanggal ang username. Nakatagpo ng error sa network. + + Too many attempts made, please try again later. Nakuha na ang username na ito. Ang usernames ay pwede lang mag-include ng a–Z, 0–9, at underscores. Ang mga username ay hindi puwedeng magsimula sa numero. diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 71696880cb..a0f94e53a2 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -2295,6 +2295,8 @@ Sil Kullanıcı adı başarıyla kaldırıldı. Bir ağ hatası ile karşılaşıldı. + + Too many attempts made, please try again later. Bu kullanıcı adı alınmış. Kullanıcı adları yalnızca a-Z, 0-9 ve alt çizgi içerebilir. Kullanıcı adları rakam ile başlayamaz. diff --git a/app/src/main/res/values-ug/strings.xml b/app/src/main/res/values-ug/strings.xml index baf510a084..811dade972 100644 --- a/app/src/main/res/values-ug/strings.xml +++ b/app/src/main/res/values-ug/strings.xml @@ -2215,6 +2215,8 @@ ئۆچۈر ئىشلەتكۈچى ئىسمى مۇۋەپپەقىيەتلىك چىقىرىۋېتىلدى. تور خاتالىقىغا يولۇقتى. + + Too many attempts made, please try again later. ئىشلەتكۈچى ئىسمى ئىشلىتىلگەن ئىشلەتكۈچى ئىسمى پەقەت a–Z، 0–9 ۋە ئاستى سىزىقتىن تۈزۈلىدۇ. ئىشلەتكۈچى ئىسمى ساندىن باشلانمايدۇ. diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 404da54fd0..2dec9d5eb0 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -2455,6 +2455,8 @@ Видалити Ім\'я користувача видалено. Виникла помилка мережі. + + Too many attempts made, please try again later. Це ім\'я користувача вже використовується. Імена користувачів можуть мати лише такі символи: a–Z, 0–9 та нижнє підкреслювання. Ім\'я користувача не може починатися з цифри. diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml index efee28eb3f..9f864f68ba 100644 --- a/app/src/main/res/values-ur/strings.xml +++ b/app/src/main/res/values-ur/strings.xml @@ -2295,6 +2295,8 @@ حذف کریں کامیابی کے ساتھ یوزر نیم ہٹا دیا گیا۔ نیٹ ورک کی خرابی کا سامنا کرنا پڑا۔ + + Too many attempts made, please try again later. یہ یوزر نیم لے لیا گیا ہے۔ یوزر نیمز میں صرف – Z-A ، 0–9 ، اور انڈر سکور شامل ہوسکتے ہیں۔ یوزر نیمز کسی نمبر کے ساتھ شروع نہیں ہوسکتے ہیں۔ diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index c5e92f9cdf..b99f8afcb4 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -2215,6 +2215,8 @@ Xóa Xóa tên người dùng thành công. Lỗi mạng. + + Too many attempts made, please try again later. Tên người dùng này đã được sử dụng. Tên người dùng chỉ có thể chứa a–Z, 0–9 và dấu gạch dưới. Tên người dùng không thể bắt đầu bằng một chữ số. diff --git a/app/src/main/res/values-yue/strings.xml b/app/src/main/res/values-yue/strings.xml index 9d6225f652..708d90d99b 100644 --- a/app/src/main/res/values-yue/strings.xml +++ b/app/src/main/res/values-yue/strings.xml @@ -2215,6 +2215,8 @@ 刪除 成功移除咗用戶名稱。 網絡有問題。 + + Too many attempts made, please try again later. 呢個用戶名稱已經畀人用咗喇。 用戶名稱只可以用 a–Z、0–9 同底線符號。 用戶名稱冇得用數目字開頭。 diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 0b4b8724f9..45c53131e2 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -2215,6 +2215,8 @@ 删除 成功移除用户名。 网络出错。 + + Too many attempts made, please try again later. 该用户名已有人使用。 用户名仅能包含字母、数字和下划线。 用户名不能以数字开头。 diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 7cc8764b98..d1696f63f4 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -2215,6 +2215,8 @@ 刪除 已成功移除用戶名稱。 遇到網絡問題。 + + Too many attempts made, please try again later. 此用戶名稱已有人使用。 用戶名稱僅可包含 a–Z、0–9 以及底線。 用戶名稱不得以數字開首。 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 06b5a35fdc..039d928105 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -2215,6 +2215,8 @@ 刪除 成功移除使用者名稱。 網路連接錯誤。 + + Too many attempts made, please try again later. 這個使用者名稱已經被使用。 使用者名稱只能包含a–Z,0–9和底線。 使用者名稱不可以以數字開頭。 diff --git a/app/static-ips.gradle.kts b/app/static-ips.gradle.kts index 7af8d8dae2..fc58eefbc6 100644 --- a/app/static-ips.gradle.kts +++ b/app/static-ips.gradle.kts @@ -1,9 +1,9 @@ rootProject.extra["service_ips"] = """new String[]{"13.248.212.111","76.223.92.165"}""" -rootProject.extra["storage_ips"] = """new String[]{"142.251.40.147"}""" +rootProject.extra["storage_ips"] = """new String[]{"142.250.80.83"}""" rootProject.extra["cdn_ips"] = """new String[]{"18.161.21.122","18.161.21.4","18.161.21.66","18.161.21.70"}""" -rootProject.extra["cdn2_ips"] = """new String[]{"104.18.10.47","104.18.11.47"}""" -rootProject.extra["cdn3_ips"] = """new String[]{"104.18.10.47","104.18.11.47"}""" -rootProject.extra["sfu_ips"] = """new String[]{"34.49.42.70"}""" +rootProject.extra["cdn2_ips"] = """new String[]{"104.18.37.148","172.64.150.108"}""" +rootProject.extra["cdn3_ips"] = """new String[]{"104.18.37.148","172.64.150.108"}""" +rootProject.extra["sfu_ips"] = """new String[]{"34.49.5.136"}""" rootProject.extra["content_proxy_ips"] = """new String[]{"107.178.250.75"}""" rootProject.extra["svr2_ips"] = """new String[]{"20.119.62.85"}""" rootProject.extra["cdsi_ips"] = """new String[]{"40.122.45.194"}""" From 940cee0f30d6a2873ae08c65bb821c34302ccf5d Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Fri, 23 Feb 2024 16:26:12 -0500 Subject: [PATCH 17/18] Bump version to 7.0.2 --- app/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ee2468f360..23b65881c5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -21,8 +21,8 @@ plugins { apply(from = "static-ips.gradle.kts") -val canonicalVersionCode = 1395 -val canonicalVersionName = "7.0.1" +val canonicalVersionCode = 1396 +val canonicalVersionName = "7.0.2" val postFixSize = 100 val abiPostFix: Map = mapOf( From e49c29fb4e75c9e3bf4e6a2387b426d99770cec8 Mon Sep 17 00:00:00 2001 From: Oscar Mira Date: Tue, 5 Mar 2024 09:24:14 +0100 Subject: [PATCH 18/18] Update Firebase API keys Fixes #291 --- app/src/main/res/values/firebase_messaging.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values/firebase_messaging.xml b/app/src/main/res/values/firebase_messaging.xml index 6cd44588a4..456df598b7 100644 --- a/app/src/main/res/values/firebase_messaging.xml +++ b/app/src/main/res/values/firebase_messaging.xml @@ -1,10 +1,10 @@ - 1:312334754206:android:a9297b152879f266 + 1:91233741263:android:0cf3d030865ac67d548aba 312334754206 312334754206-dg1p1mtekis8ivja3ica50vonmrlunh4.apps.googleusercontent.com https://api-project-312334754206.firebaseio.com - AIzaSyDrfzNAPBPzX6key51hqo3p5LZXF5Y-yxU - AIzaSyDrfzNAPBPzX6key51hqo3p5LZXF5Y-yxU - api-project-312334754206 + AIzaSyAuRxbDoMcC_637-4_hQfXQssLq0xgPlbQ + AIzaSyAuRxbDoMcC_637-4_hQfXQssLq0xgPlbQ + mollyim-365011 \ No newline at end of file