diff --git a/app/build.gradle b/app/build.gradle index 791f8a884a..9480b4621c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -83,8 +83,8 @@ ktlint { version = "0.43.2" } -def canonicalVersionCode = 988 -def canonicalVersionName = "5.29.4" +def canonicalVersionCode = 990 +def canonicalVersionName = "5.29.6" def mollyRevision = 0 def postFixSize = 100 diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 2c088024ad..1da0f92d8f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -39,6 +39,7 @@ import org.signal.core.util.logging.Log; import org.signal.core.util.tracing.Tracer; import org.signal.glide.SignalGlideCodecs; +import org.thoughtcrime.securesms.emoji.JumboEmoji; import org.thoughtcrime.securesms.mms.SignalGlideModule; import org.signal.ringrtc.CallManager; import org.thoughtcrime.securesms.avatar.AvatarPickerStorage; @@ -209,6 +210,7 @@ private void onCreateUnlock() { .addPostRender(() -> DownloadLatestEmojiDataJob.scheduleIfNecessary(this)) .addPostRender(EmojiSearchIndexDownloadJob::scheduleIfNecessary) .addPostRender(() -> SignalDatabase.messageLog().trimOldMessages(System.currentTimeMillis(), FeatureFlags.retryRespondMaxAge())) + .addPostRender(() -> JumboEmoji.updateCurrentVersion(this)) .execute(); Log.d(TAG, "onCreateUnlock() took " + (System.currentTimeMillis() - startTime) + " ms"); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/Emoji.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/Emoji.java index 155d541594..6935bf17ab 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/Emoji.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/Emoji.java @@ -1,18 +1,27 @@ package org.thoughtcrime.securesms.components.emoji; +import androidx.annotation.Nullable; + import java.util.Arrays; +import java.util.Collections; import java.util.List; public class Emoji { private final List variations; + private final List rawVariations; public Emoji(String... variations) { - this.variations = Arrays.asList(variations); + this(Arrays.asList(variations), Collections.emptyList()); } public Emoji(List variations) { + this(variations, Collections.emptyList()); + } + + public Emoji(List variations, List rawVariations) { this.variations = variations; + this.rawVariations = rawVariations; } public String getValue() { @@ -26,4 +35,11 @@ public List getVariations() { public boolean hasMultipleVariations() { return variations.size() > 1; } + + public @Nullable String getRawVariation(int variationIndex) { + if (rawVariations != null && variationIndex >= 0 && variationIndex < rawVariations.size()) { + return rawVariations.get(variationIndex); + } + return null; + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java index e53bcbba6c..c7b1b9437e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java @@ -149,8 +149,8 @@ public void onFailure(ExecutionException exception) { throw new IllegalStateException("Unexpected subclass " + loadResult.getClass()); } - if (jumboEmoji) { - JumboEmoji.LoadResult result = JumboEmoji.loadJumboEmoji(context, drawInfo.getRawEmoji()); + if (jumboEmoji && drawInfo.getJumboSheet() != null) { + JumboEmoji.LoadResult result = JumboEmoji.loadJumboEmoji(context, drawInfo); if (result instanceof JumboEmoji.LoadResult.Immediate) { ThreadUtil.runOnMain(() -> { jumboLoaded.set(true); @@ -171,7 +171,11 @@ public void onSuccess(Bitmap result) { @Override public void onFailure(ExecutionException exception) { - Log.d(TAG, "Failed to load jumbo emoji bitmap resource", exception); + if (exception.getCause() instanceof JumboEmoji.CannotAutoDownload) { + Log.i(TAG, "Download restrictions are preventing jumbomoji use"); + } else { + Log.d(TAG, "Failed to load jumbo emoji bitmap resource", exception); + } } }); } @@ -200,15 +204,19 @@ public void onFailure(ExecutionException exception) { Bitmap bitmap = null; - if (jumboEmoji) { - JumboEmoji.LoadResult result = JumboEmoji.loadJumboEmoji(context, drawInfo.getRawEmoji()); + if (jumboEmoji && drawInfo.getJumboSheet() != null) { + JumboEmoji.LoadResult result = JumboEmoji.loadJumboEmoji(context, drawInfo); if (result instanceof JumboEmoji.LoadResult.Immediate) { bitmap = ((JumboEmoji.LoadResult.Immediate) result).getBitmap(); } else if (result instanceof JumboEmoji.LoadResult.Async) { try { bitmap = ((JumboEmoji.LoadResult.Async) result).getTask().get(10, TimeUnit.SECONDS); } catch (InterruptedException | ExecutionException | TimeoutException exception) { - Log.d(TAG, "Failed to load jumbo emoji bitmap resource", exception); + if (exception.getCause() instanceof JumboEmoji.CannotAutoDownload) { + Log.i(TAG, "Download restrictions are preventing jumbomoji use"); + } else { + Log.d(TAG, "Failed to load jumbo emoji bitmap resource", exception); + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java index f7c1859f65..d5bbf0f180 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java @@ -30,6 +30,7 @@ import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser; import org.thoughtcrime.securesms.components.mention.MentionAnnotation; import org.thoughtcrime.securesms.components.mention.MentionRendererDelegate; +import org.thoughtcrime.securesms.emoji.JumboEmoji; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; @@ -43,7 +44,8 @@ public class EmojiTextView extends AppCompatTextView { private final boolean scaleEmojis; - private static final char ELLIPSIS = '…'; + private static final char ELLIPSIS = '…'; + private static final float JUMBOMOJI_SCALE = 0.8f; private boolean forceCustom; private CharSequence previousText; @@ -113,13 +115,13 @@ protected void onDraw(Canvas canvas) { public void setText(@Nullable CharSequence text, BufferType type) { EmojiParser.CandidateList candidates = isInEditMode() ? null : EmojiProvider.getCandidates(text); - if (scaleEmojis && candidates != null && candidates.allEmojis) { + if (scaleEmojis && candidates != null && candidates.allEmojis && (candidates.hasJumboForAll() || JumboEmoji.canDownloadJumbo(getContext()))) { int emojis = candidates.size(); float scale = 1.0f; - if (emojis <= 5) scale += 0.9f; - if (emojis <= 4) scale += 0.9f; - if (emojis <= 2) scale += 0.9f; + if (emojis <= 5) scale += JUMBOMOJI_SCALE; + if (emojis <= 4) scale += JUMBOMOJI_SCALE; + if (emojis <= 2) scale += JUMBOMOJI_SCALE; isJumbomoji = scale > 1.0f; super.setTextSize(TypedValue.COMPLEX_UNIT_PX, originalFontSize * scale); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiDrawInfo.kt b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiDrawInfo.kt index a19fa638ba..28de3aca78 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiDrawInfo.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiDrawInfo.kt @@ -1,13 +1,5 @@ package org.thoughtcrime.securesms.components.emoji.parsing import org.thoughtcrime.securesms.emoji.EmojiPage -import org.thoughtcrime.securesms.util.Hex -import java.nio.charset.Charset -data class EmojiDrawInfo(val page: EmojiPage, val index: Int, private val emoji: String) { - val rawEmoji: String - get() { - val emojiBytes: ByteArray = emoji.toByteArray(Charset.forName("UTF-16")) - return Hex.toStringCondensed(emojiBytes.slice(2 until emojiBytes.size).toByteArray()) - } -} +data class EmojiDrawInfo(val page: EmojiPage, val index: Int, private val emoji: String, val rawEmoji: String?, val jumboSheet: String?) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiParser.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiParser.java index 91450e102b..ced9178683 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiParser.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiParser.java @@ -24,6 +24,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.thoughtcrime.securesms.emoji.JumboEmoji; + import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -127,6 +129,15 @@ public int size() { return list.size(); } + public boolean hasJumboForAll() { + for (Candidate candidate : list) { + if (!JumboEmoji.hasJumboEmoji(candidate.drawInfo)) { + return false; + } + } + return true; + } + @Override public @NonNull Iterator iterator() { return list.iterator(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index a262dcda73..cb662abc9f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -3560,14 +3560,14 @@ public void handleReaction(@NonNull ConversationMessage conversationMessage, @Override public void onMessageWithErrorClicked(@NonNull MessageRecord messageRecord) { - if (messageRecord.hasFailedWithNetworkFailures()) { + if (messageRecord.isIdentityMismatchFailure()) { + SafetyNumberChangeDialog.show(this, messageRecord); + } else if (messageRecord.hasFailedWithNetworkFailures()) { new AlertDialog.Builder(this) .setMessage(R.string.conversation_activity__message_could_not_be_sent) .setNegativeButton(android.R.string.cancel, null) .setPositiveButton(R.string.conversation_activity__send, (dialog, which) -> MessageSender.resend(this, messageRecord)) .show(); - } else if (messageRecord.isIdentityMismatchFailure()) { - SafetyNumberChangeDialog.show(this, messageRecord); } else { startActivity(MessageDetailsActivity.getIntentForMessageDetails(this, messageRecord, messageRecord.getRecipient().getId(), messageRecord.getThreadId())); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index 9f494f7a67..df0238be5e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -849,9 +849,11 @@ public void markAsRemoteDelete(long messageId) { deletedAttachments = SignalDatabase.attachments().deleteAttachmentsForMessage(messageId); SignalDatabase.mentions().deleteMentionsForMessage(messageId); SignalDatabase.messageLog().deleteAllRelatedToMessage(messageId, true); + SignalDatabase.reactions().deleteReactions(new MessageId(messageId, true)); threadId = getThreadIdForMessage(messageId); SignalDatabase.threads().update(threadId, false); + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt index 6ce8ce7a75..d80e6da5fc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt @@ -168,6 +168,10 @@ class ReactionDatabase(context: Context, databaseHelper: SignalDatabase) : Datab ApplicationDependencies.getDatabaseObserver().notifyMessageUpdateObservers(messageId) } + fun deleteReactions(messageId: MessageId) { + writableDatabase.delete(TABLE_NAME, "$MESSAGE_ID = ? AND $IS_MMS = ?", SqlUtil.buildArgs(messageId.id, if (messageId.mms) 1 else 0)) + } + fun hasReaction(messageId: MessageId, reaction: ReactionRecord): Boolean { val query = "$MESSAGE_ID = ? AND $IS_MMS = ? AND $AUTHOR_ID = ? AND $EMOJI = ?" val args = SqlUtil.buildArgs(messageId.id, if (messageId.mms) 1 else 0, reaction.author, reaction.emoji) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index 59a0417d05..1f2724364b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -392,6 +392,7 @@ public void markAsRemoteDelete(long id) { threadId = getThreadIdForMessage(id); + SignalDatabase.reactions().deleteReactions(new MessageId(id, false)); SignalDatabase.threads().update(threadId, false); SignalDatabase.messageLog().deleteAllRelatedToMessage(id, false); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt index dc78dfbc08..bb8d472716 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt @@ -132,8 +132,9 @@ object SignalDatabaseMigrations { private const val NOTIFICATION_PROFILES = 123 private const val NOTIFICATION_PROFILES_END_FIX = 124 private const val REACTION_BACKUP_CLEANUP = 125 + private const val REACTION_REMOTE_DELETE_CLEANUP = 126 - const val DATABASE_VERSION = 125 + const val DATABASE_VERSION = 126 @JvmStatic fun migrate(context: Context, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { @@ -1643,6 +1644,19 @@ object SignalDatabaseMigrations { """.trimIndent() ) } + + if (oldVersion < REACTION_REMOTE_DELETE_CLEANUP) { + db.execSQL( + // language=sql + """ + DELETE FROM reaction + WHERE + (is_mms = 0 AND message_id IN (SELECT _id from sms WHERE remote_deleted = 1)) + OR + (is_mms = 1 AND message_id IN (SELECT _id from mms WHERE remote_deleted = 1)) + """.trimIndent() + ) + } } @JvmStatic diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index 506bd6815f..76bf99b33d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -612,7 +612,7 @@ public boolean isJumbomoji(Context context) { if (isJumboji == null) { if (getBody().length() <= EmojiSource.getLatest().getMaxEmojiLength() * JumboEmoji.MAX_JUMBOJI_COUNT) { EmojiParser.CandidateList candidates = EmojiProvider.getCandidates(getDisplayBody(context)); - isJumboji = candidates != null && candidates.allEmojis && candidates.size() <= JumboEmoji.MAX_JUMBOJI_COUNT; + isJumboji = candidates != null && candidates.allEmojis && candidates.size() <= JumboEmoji.MAX_JUMBOJI_COUNT && (candidates.hasJumboForAll() || JumboEmoji.canDownloadJumbo(context)); } else { isJumboji = false; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiDownloader.java b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiDownloader.java index 920ca7460a..4c1bd3ab57 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiDownloader.java +++ b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiDownloader.java @@ -4,10 +4,12 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.util.Consumer; import com.mobilecoin.lib.util.Hex; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; import java.util.UUID; @@ -44,6 +46,16 @@ public class EmojiDownloader { () -> new EmojiFiles.Name(imagePath, UUID.randomUUID())); } + public static void streamFileFromRemote(@NonNull EmojiFiles.Version version, + @NonNull String bucket, + @NonNull String path, + @NonNull Consumer streamConsumer) + throws IOException + { + streamFromRemote(() -> EmojiRemote.getObject(new EmojiFileRequest(version.getVersion(), bucket, path)), + streamConsumer); + } + private static @NonNull EmojiFiles.Name downloadAndVerifyFromRemote(@NonNull Context context, @NonNull EmojiFiles.Version version, @NonNull Producer responseProducer, @@ -90,6 +102,23 @@ public class EmojiDownloader { } } + private static void streamFromRemote(@NonNull Producer responseProducer, + @NonNull Consumer streamConsumer) throws IOException + { + try (Response response = responseProducer.produce()) { + if (!response.isSuccessful()) { + throw new IOException("Unsuccessful response " + response.code()); + } + + ResponseBody responseBody = response.body(); + if (responseBody == null) { + throw new IOException("No response body"); + } + + streamConsumer.accept(Okio.buffer(responseBody.source()).inputStream()); + } + } + private static @Nullable String getMD5FromResponse(@NonNull Response response) { Pattern pattern = Pattern.compile(".*([a-f0-9]{32}).*"); String header = response.header("etag"); diff --git a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiJsonParser.kt b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiJsonParser.kt index 78cb318a6a..c8f7d1d010 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiJsonParser.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiJsonParser.kt @@ -18,6 +18,7 @@ typealias UriFactory = (sprite: String, format: String) -> Uri */ object EmojiJsonParser { private val OBJECT_MAPPER = ObjectMapper() + private const val ESTIMATED_EMOJI_COUNT = 3500 @JvmStatic fun verify(body: InputStream) { @@ -36,11 +37,12 @@ object EmojiJsonParser { val format: String = node["format"].textValue() val obsolete: List = node["obsolete"].toObseleteList() val dataPages: List = getDataPages(format, node["emoji"], uriFactory) + val jumboPages: Map = getJumboPages(node["jumbomoji"]) val displayPages: List = mergeToDisplayPages(dataPages) val metrics: EmojiMetrics = node["metrics"].toEmojiMetrics() val densities: List = node["densities"].toDensityList() - return ParsedEmojiData(metrics, densities, format, displayPages, dataPages, obsolete) + return ParsedEmojiData(metrics, densities, format, displayPages, dataPages, jumboPages, obsolete) } private fun getDataPages(format: String, emoji: JsonNode, uriFactory: UriFactory): List { @@ -64,13 +66,33 @@ object EmojiJsonParser { .toList() } + private fun getJumboPages(jumbo: JsonNode?): Map { + if (jumbo != null) { + return jumbo.fields() + .asSequence() + .map { (page: String, node: JsonNode) -> + node.associate { it.textValue() to page } + } + .flatMap { it.entries } + .associateTo(HashMap(ESTIMATED_EMOJI_COUNT)) { it.key to it.value } + } + return emptyMap() + } + private fun createPage(pageName: String, format: String, page: JsonNode, uriFactory: UriFactory): EmojiPageModel { val category = EmojiCategory.forKey(pageName.asCategoryKey()) val pageList = page.mapIndexed { i, data -> if (data.size() == 0) { throw IllegalStateException("Page index $pageName.$i had no data") } else { - Emoji(data.map { it.textValue().encodeAsUtf16() }) + val variations: MutableList = mutableListOf() + val rawVariations: MutableList = mutableListOf() + data.forEach { + variations += it.textValue().encodeAsUtf16() + rawVariations += it.textValue() + } + + Emoji(variations, rawVariations) } } @@ -111,5 +133,6 @@ data class ParsedEmojiData( override val format: String, override val displayPages: List, override val dataPages: List, + override val jumboPages: Map, override val obsolete: List ) : EmojiData diff --git a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiRemote.kt b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiRemote.kt index 7c1888222a..e3b75d3ade 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiRemote.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiRemote.kt @@ -6,7 +6,7 @@ import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import java.io.IOException -private const val VERSION_URL = "https://updates.signal.org/dynamic/android/emoji/version_v2.txt" +private const val VERSION_URL = "https://updates.signal.org/dynamic/android/emoji/version_v3.txt" private const val BASE_STATIC_BUCKET_URL = "https://updates.signal.org/static/android/emoji" /** @@ -93,3 +93,11 @@ class EmojiImageRequest( ) : EmojiRequest { override val url: String = "$BASE_STATIC_BUCKET_URL/$version/$density/$name.$format" } + +class EmojiFileRequest( + version: Int, + density: String, + name: String, +) : EmojiRequest { + override val url: String = "$BASE_STATIC_BUCKET_URL/$version/$density/$name" +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiSource.kt b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiSource.kt index aa1032f454..91d8f09187 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiSource.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiSource.kt @@ -62,8 +62,13 @@ class EmojiSource( .filter { it.spriteUri != null } .forEach { page -> val emojiPage = emojiPageFactory(page.spriteUri!!) - page.emoji.forEachIndexed { idx, emoji -> - tree.add(emoji, EmojiDrawInfo(emojiPage, idx, emoji)) + + var overallIndex = 0 + page.displayEmoji.forEach { emoji: Emoji -> + emoji.variations.forEachIndexed { variationIndex, variation -> + val raw = emoji.getRawVariation(variationIndex) + tree.add(variation, EmojiDrawInfo(emojiPage, overallIndex++, variation, raw, jumboPages[raw])) + } } } @@ -142,6 +147,7 @@ interface EmojiData { val format: String val displayPages: List val dataPages: List + val jumboPages: Map val obsolete: List } diff --git a/app/src/main/java/org/thoughtcrime/securesms/emoji/JumboEmoji.kt b/app/src/main/java/org/thoughtcrime/securesms/emoji/JumboEmoji.kt index f22231e5f5..4d5724f3ac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/emoji/JumboEmoji.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/emoji/JumboEmoji.kt @@ -3,16 +3,22 @@ package org.thoughtcrime.securesms.emoji import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory +import android.os.SystemClock import androidx.annotation.MainThread +import org.signal.core.util.ThreadUtil import org.signal.core.util.concurrent.SignalExecutors import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.components.emoji.parsing.EmojiDrawInfo +import org.thoughtcrime.securesms.emoji.protos.JumbomojiPack import org.thoughtcrime.securesms.jobmanager.impl.AutoDownloadEmojiConstraint +import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.util.ListenableFutureTask import org.thoughtcrime.securesms.util.SoftHashMap import org.thoughtcrime.securesms.util.concurrent.SimpleTask import java.io.IOException import java.util.UUID import java.util.concurrent.ExecutionException +import java.util.concurrent.TimeUnit private val TAG = Log.tag(JumboEmoji::class.java) @@ -21,30 +27,67 @@ private val TAG = Log.tag(JumboEmoji::class.java) */ object JumboEmoji { + private val executor = ThreadUtil.trace(SignalExecutors.newCachedSingleThreadExecutor("jumbo-emoji")) + const val MAX_JUMBOJI_COUNT = 5 + private const val JUMBOMOJI_SUPPORTED_VERSION = 5 + private val cache: MutableMap = SoftHashMap(16) - private val tasks: MutableMap> = hashMapOf() private val versionToFormat: MutableMap = hashMapOf() + private val downloadedJumbos: MutableSet = mutableSetOf() + + private val networkCheckThrottle: Long = TimeUnit.MINUTES.toMillis(1) + private var lastNetworkCheck: Long = 0 + private var canDownload: Boolean = false + + @Volatile + private var currentVersion: Int = -1 + + @JvmStatic + @MainThread + fun updateCurrentVersion(context: Context) { + SignalExecutors.BOUNDED.execute { + val version: EmojiFiles.Version = EmojiFiles.Version.readVersion(context, true) ?: return@execute + + if (EmojiFiles.getLatestEmojiData(context, version)?.format != null) { + currentVersion = version.version + ThreadUtil.runOnMain { downloadedJumbos.addAll(SignalStore.emojiValues().getJumboEmojiSheets(version.version)) } + } + } + } + + @JvmStatic + @MainThread + fun canDownloadJumbo(context: Context): Boolean { + val now = SystemClock.elapsedRealtime() + if (now - networkCheckThrottle > lastNetworkCheck) { + canDownload = AutoDownloadEmojiConstraint.canAutoDownloadJumboEmoji(context) + lastNetworkCheck = now + } + return canDownload && currentVersion >= JUMBOMOJI_SUPPORTED_VERSION + } + + @JvmStatic + @MainThread + fun hasJumboEmoji(drawInfo: EmojiDrawInfo): Boolean { + return downloadedJumbos.contains(drawInfo.jumboSheet) + } @Suppress("FoldInitializerAndIfToElvis") @JvmStatic @MainThread - fun loadJumboEmoji(context: Context, rawEmoji: String): LoadResult { + fun loadJumboEmoji(context: Context, drawInfo: EmojiDrawInfo): LoadResult { val applicationContext: Context = context.applicationContext - val name: String = "jumbo/$rawEmoji" - val bitmap: Bitmap? = cache[name] - val task: ListenableFutureTask? = tasks[name] + val archiveName = "jumbo/${drawInfo.jumboSheet}.proto" + val emojiName: String = drawInfo.rawEmoji!! + val bitmap: Bitmap? = cache[emojiName] if (bitmap != null) { return LoadResult.Immediate(bitmap) } - if (task != null) { - return LoadResult.Async(task) - } - val newTask = ListenableFutureTask { val version: EmojiFiles.Version? = EmojiFiles.Version.readVersion(applicationContext, true) if (version == null) { @@ -59,38 +102,50 @@ object JumboEmoji { throw NoVersionData() } + currentVersion = version.version + var jumbos: EmojiFiles.JumboCollection = EmojiFiles.JumboCollection.read(applicationContext, version) - val uuid = jumbos.getUUIDForName(name) + val uuid = jumbos.getUUIDForName(emojiName) if (uuid == null) { - if (!AutoDownloadEmojiConstraint.canAutoDownloadEmoji(applicationContext)) { + if (!AutoDownloadEmojiConstraint.canAutoDownloadJumboEmoji(applicationContext)) { throw CannotAutoDownload() } Log.i(TAG, "No file for emoji, downloading jumbo") - val emojiFilesName: EmojiFiles.Name = EmojiDownloader.downloadAndVerifyImageFromRemote(applicationContext, version, version.density, name, format) - jumbos = EmojiFiles.JumboCollection.append(applicationContext, jumbos, emojiFilesName) + EmojiDownloader.streamFileFromRemote(version, version.density, archiveName) { stream -> + stream.use { remote -> + val jumbomojiPack = JumbomojiPack.parseFrom(remote) + + jumbomojiPack.itemsList.forEach { jumbo -> + val emojiNameEntry = EmojiFiles.Name(jumbo.name, UUID.randomUUID()) + val outputStream = EmojiFiles.openForWriting(applicationContext, version, emojiNameEntry.uuid) + + outputStream.use { jumbo.image.writeTo(it) } + + jumbos = EmojiFiles.JumboCollection.append(applicationContext, jumbos, emojiNameEntry) + } + } + } + + SignalStore.emojiValues().addJumboEmojiSheet(version.version, drawInfo.jumboSheet) } - val inputStream = EmojiFiles.openForReadingJumbo(applicationContext, version, jumbos, name) - inputStream.use { BitmapFactory.decodeStream(it, null, BitmapFactory.Options()) } + EmojiFiles.openForReadingJumbo(applicationContext, version, jumbos, emojiName).use { BitmapFactory.decodeStream(it, null, BitmapFactory.Options()) } } - tasks[name] = newTask - - SimpleTask.run(SignalExecutors.SERIAL, newTask::run) { + SimpleTask.run(executor, newTask::run) { try { val newBitmap: Bitmap? = newTask.get() if (newBitmap == null) { Log.w(TAG, "Failed to load jumbo emoji") } else { - cache[name] = newBitmap + cache[emojiName] = newBitmap + downloadedJumbos.add(drawInfo.jumboSheet!!) } } catch (e: ExecutionException) { - Log.d(TAG, "Failed to load jumbo emoji", e.cause) - } finally { - tasks.remove(name) + // do nothing, emoji provider will log the exception } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/AutoDownloadEmojiConstraint.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/AutoDownloadEmojiConstraint.java index 94ff035ab4..a86927598a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/AutoDownloadEmojiConstraint.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/AutoDownloadEmojiConstraint.java @@ -54,11 +54,15 @@ public void applyToJobInfo(@NonNull JobInfo.Builder jobInfoBuilder) { } public static boolean canAutoDownloadEmoji(@NonNull Context context) { - return getAllowedAutoDownloadTypes(context).contains(IMAGE_TYPE); + return getAllowedAutoDownloadTypes(context, true).contains(IMAGE_TYPE); } - private static @NonNull Set getAllowedAutoDownloadTypes(@NonNull Context context) { - if (NetworkUtil.isConnectedWifi(context)) return Collections.singleton(IMAGE_TYPE); + public static boolean canAutoDownloadJumboEmoji(@NonNull Context context) { + return getAllowedAutoDownloadTypes(context, false).contains(IMAGE_TYPE); + } + + private static @NonNull Set getAllowedAutoDownloadTypes(@NonNull Context context, boolean forceWifi) { + if (NetworkUtil.isConnectedWifi(context)) return forceWifi ? Collections.singleton(IMAGE_TYPE) : TextSecurePreferences.getWifiMediaDownloadAllowed(context); else if (NetworkUtil.isConnectedRoaming(context)) return TextSecurePreferences.getRoamingMediaDownloadAllowed(context); else if (NetworkUtil.isConnectedMobile(context)) return TextSecurePreferences.getMobileMediaDownloadAllowed(context); else return Collections.emptySet(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/DownloadLatestEmojiDataJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/DownloadLatestEmojiDataJob.java index b7e2a81742..e9ca56468e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/DownloadLatestEmojiDataJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/DownloadLatestEmojiDataJob.java @@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.emoji.EmojiPageCache; import org.thoughtcrime.securesms.emoji.EmojiRemote; import org.thoughtcrime.securesms.emoji.EmojiSource; +import org.thoughtcrime.securesms.emoji.JumboEmoji; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.AutoDownloadEmojiConstraint; @@ -158,6 +159,7 @@ protected void onRun() throws Exception { clearOldEmojiData(context, targetVersion); markComplete(targetVersion); EmojiSource.refresh(); + JumboEmoji.updateCurrentVersion(context); } else { Log.d(TAG, "Server has an older version than we do. Skipping."); } @@ -359,6 +361,10 @@ private static void clearOldEmojiData(@NonNull Context context, @Nullable EmojiF .forEach(FileUtils::deleteDirectory); EmojiPageCache.INSTANCE.clear(); + + if (version != null) { + SignalStore.emojiValues().clearJumboEmojiSheets(version.getVersion()); + } } public static final class Factory implements Job.Factory { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index f4b6658cfe..cb50884e21 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -37,6 +37,7 @@ import org.thoughtcrime.securesms.migrations.DatabaseMigrationJob; import org.thoughtcrime.securesms.migrations.DeleteDeprecatedLogsMigrationJob; import org.thoughtcrime.securesms.migrations.DirectoryRefreshMigrationJob; +import org.thoughtcrime.securesms.migrations.EmojiDownloadMigrationJob; import org.thoughtcrime.securesms.migrations.KbsEnclaveMigrationJob; import org.thoughtcrime.securesms.migrations.MigrationCompleteJob; import org.thoughtcrime.securesms.migrations.PassingMigrationJob; @@ -168,6 +169,7 @@ public static Map getJobFactories(@NonNull Application appl put(DatabaseMigrationJob.KEY, new DatabaseMigrationJob.Factory()); put(DeleteDeprecatedLogsMigrationJob.KEY, new DeleteDeprecatedLogsMigrationJob.Factory()); put(DirectoryRefreshMigrationJob.KEY, new DirectoryRefreshMigrationJob.Factory()); + put(EmojiDownloadMigrationJob.KEY, new EmojiDownloadMigrationJob.Factory()); put(KbsEnclaveMigrationJob.KEY, new KbsEnclaveMigrationJob.Factory()); put(MigrationCompleteJob.KEY, new MigrationCompleteJob.Factory()); put(PinOptOutMigration.KEY, new PinOptOutMigration.Factory()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileKeySendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileKeySendJob.java index c947dd7e51..2941e45052 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileKeySendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileKeySendJob.java @@ -65,16 +65,17 @@ public static ProfileKeySendJob create(@NonNull Context context, long threadId, if (queueLimits) { return new ProfileKeySendJob(new Parameters.Builder() - .setQueue(conversationRecipient.getId().toQueueKey()) + .setQueue("ProfileKeySendJob_" + conversationRecipient.getId().toQueueKey()) + .setMaxInstancesForQueue(1) .addConstraint(NetworkConstraint.KEY) + .addConstraint(DecryptionsDrainedConstraint.KEY) .setLifespan(TimeUnit.DAYS.toMillis(1)) .setMaxAttempts(Parameters.UNLIMITED) .build(), threadId, recipients); } else { return new ProfileKeySendJob(new Parameters.Builder() - .setQueue("ProfileKeySendJob_" + conversationRecipient.getId().toQueueKey()) + .setQueue(conversationRecipient.getId().toQueueKey()) .addConstraint(NetworkConstraint.KEY) - .addConstraint(DecryptionsDrainedConstraint.KEY) .setLifespan(TimeUnit.DAYS.toMillis(1)) .setMaxAttempts(Parameters.UNLIMITED) .build(), threadId, recipients); diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/EmojiValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/EmojiValues.java index cbf9018145..cd23edda63 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/EmojiValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/EmojiValues.java @@ -11,7 +11,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; public class EmojiValues extends SignalStoreValues { @@ -28,6 +30,7 @@ public class EmojiValues extends SignalStoreValues { private static final String SEARCH_VERSION = PREFIX + "search_version"; private static final String SEARCH_LANGUAGE = PREFIX + "search_language"; private static final String LAST_SEARCH_CHECK = PREFIX + "last_search_check"; + private static final String JUMBO_EMOJI_DOWNLOAD = PREFIX + "jumbo_emoji_v"; public static final String NO_LANGUAGE = "NO_LANGUAGE"; @@ -131,4 +134,22 @@ public long getLastSearchIndexCheck() { public void setLastSearchIndexCheck(long time) { putLong(LAST_SEARCH_CHECK, time); } + + public void addJumboEmojiSheet(int version, String sheet) { + Set sheets = getJumboEmojiSheets(version); + sheets.add(sheet); + getStore().beginWrite() + .putString(JUMBO_EMOJI_DOWNLOAD + version, Util.join(sheets, ",")) + .apply(); + } + + public HashSet getJumboEmojiSheets(int version) { + return new HashSet<>(Arrays.asList(getStore().getString(JUMBO_EMOJI_DOWNLOAD + version, "").split(","))); + } + + public void clearJumboEmojiSheets(int version) { + getStore().beginWrite() + .remove(JUMBO_EMOJI_DOWNLOAD + version) + .apply(); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java index 426c5c0d64..21c41d4558 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java @@ -285,7 +285,8 @@ private void handleMessage(@NonNull SignalServiceContent content, long timestamp .enqueue(); } else if (!threadRecipient.isGroup()) { Log.i(TAG, "Message was to a 1:1. Ensuring this user has our profile key."); - ApplicationDependencies.getJobManager().startChain(new RefreshAttributesJob(false)) + ApplicationDependencies.getJobManager() + .startChain(new RefreshAttributesJob(false)) .then(ProfileKeySendJob.create(context, SignalDatabase.threads().getOrCreateThreadIdFor(threadRecipient), true)) .enqueue(); } @@ -1722,13 +1723,12 @@ private void handleProfileKey(@NonNull SignalServiceContent content, @NonNull byte[] messageProfileKeyBytes, @NonNull Recipient senderRecipient) { - log(content.getTimestamp(), "Profile key."); - RecipientDatabase database = SignalDatabase.recipients(); ProfileKey messageProfileKey = ProfileKeyUtil.profileKeyOrNull(messageProfileKeyBytes); if (messageProfileKey != null) { if (database.setProfileKey(senderRecipient.getId(), messageProfileKey)) { + log(content.getTimestamp(), "Profile key on message from " + senderRecipient.getId() + " didn't match our local store. It has been updated."); ApplicationDependencies.getJobManager().add(RetrieveProfileJob.forRecipient(senderRecipient.getId())); } } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java index db35a8cf50..3fad1d2d42 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java @@ -25,7 +25,7 @@ * Manages application-level migrations. * * Migrations can be slotted to occur based on changes in the canonical version code - * (see {@link Util#getCanonicalVersionCode()}). + * (see {@link Util#getSignalCanonicalVersionCode()}). * * Migrations are performed via {@link MigrationJob}s. These jobs are durable and are run before any * other job, allowing you to schedule safe migrations. Furthermore, you may specify that a @@ -84,9 +84,11 @@ static final class Version { //static final int CHANGE_NUMBER_CAPABILITY_3 = 49; static final int PNI = 50; static final int FIX_DEPRECATION = 51; // Only used to trigger clearing the 'client deprecated' flag + //static final int JUMBOMOJI_DOWNLOAD = 52; + static final int FIX_EMOJI_QUALITY = 53; } - public static final int CURRENT_VERSION = 51; + public static final int CURRENT_VERSION = 53; /** * This *must* be called after the {@link JobManager} has been instantiated, but *before* the call @@ -355,6 +357,10 @@ private static LinkedHashMap getMigrationJobs(@NonNull Co jobs.put(Version.PNI, new PniMigrationJob()); } + if (lastSeenVersion < Version.FIX_EMOJI_QUALITY) { + jobs.put(Version.FIX_EMOJI_QUALITY, new EmojiDownloadMigrationJob()); + } + return jobs; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/EmojiDownloadMigrationJob.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/EmojiDownloadMigrationJob.java new file mode 100644 index 0000000000..d9bbebf78c --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/EmojiDownloadMigrationJob.java @@ -0,0 +1,51 @@ +package org.thoughtcrime.securesms.migrations; + +import androidx.annotation.NonNull; + +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.jobmanager.Data; +import org.thoughtcrime.securesms.jobmanager.Job; +import org.thoughtcrime.securesms.jobs.DownloadLatestEmojiDataJob; + +/** + * Schedules a emoji download job to get the latest version. + */ +public final class EmojiDownloadMigrationJob extends MigrationJob { + + public static final String KEY = "EmojiDownloadMigrationJob"; + + EmojiDownloadMigrationJob() { + this(new Parameters.Builder().build()); + } + + private EmojiDownloadMigrationJob(@NonNull Parameters parameters) { + super(parameters); + } + + @Override + public boolean isUiBlocking() { + return false; + } + + @Override + public @NonNull String getFactoryKey() { + return KEY; + } + + @Override + public void performMigration() { + ApplicationDependencies.getJobManager().add(new DownloadLatestEmojiDataJob(false)); + } + + @Override + boolean shouldRetry(@NonNull Exception e) { + return false; + } + + public static class Factory implements Job.Factory { + @Override + public @NonNull EmojiDownloadMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) { + return new EmojiDownloadMigrationJob(parameters); + } + } +} diff --git a/app/src/main/proto/Emoji.proto b/app/src/main/proto/Emoji.proto new file mode 100644 index 0000000000..8cd0006227 --- /dev/null +++ b/app/src/main/proto/Emoji.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package signal; + +option java_package = "org.thoughtcrime.securesms.emoji.protos"; +option java_multiple_files = true; + +message JumbomojiPack { + repeated JumbomojiItem items = 1; +} + +message JumbomojiItem { + string name = 1; + bytes image = 2; +} diff --git a/app/src/main/res/drawable/ic_video_solid_24_tinted.xml b/app/src/main/res/drawable/ic_video_solid_24_tinted.xml index d48cd6d77b..95ef92483c 100644 --- a/app/src/main/res/drawable/ic_video_solid_24_tinted.xml +++ b/app/src/main/res/drawable/ic_video_solid_24_tinted.xml @@ -5,5 +5,5 @@ android:viewportHeight="24"> + android:pathData="m23,8.06v7.88c0.0018,0.7313 -0.7563,1.2172 -1.42,0.91L18,15.2V8.8L21.58,7.15C22.2437,6.8428 23.0018,7.3287 23,8.06ZM16.5,17V7c0,-1.6569 -1.3431,-3 -3,-3H4C2.3431,4 1,5.3431 1,7v10c0,1.6569 1.3431,3 3,3h9.5c1.6569,0 3,-1.3431 3,-3z"/> diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 3720ad0c18..b3c5a76fd0 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -778,6 +778,7 @@ Изглежда нямате приложения, с които да можете да сподялите. Научете повече + Прочети повече Съобщението не е намерено Съобщение от %1$s @@ -1046,6 +1047,7 @@ Отбелязахте числото си за сигурност с %s като непотвърдени Отбелязахте числот си за сугорност с %s като непотвърдени от друго устройство Съобщение от %s не успя да бъде доставено + %1$s смени номера си с нов номер. %1$s започна групово разговор · %2$s %1$s е в групов разговор · %2$s @@ -1301,6 +1303,7 @@ Условия и Политика за поверителност Направихте прекалено много опити за регистриране на този номер. Моля, опитайте отново по-късно. Неуспешно свързване с услугата. Моля, проверете мрежовата връзка и опитайте отново. + Нестандартен формат на номера Сега сте %d стъпка от изпращането на дебъг лога. Сега сте на %d стъпки от изпращането на дебъг лог. @@ -1841,6 +1844,9 @@ Приплъзнете надолу, за да отговорите Няколко проблема изискват вашето внимание. + Изпратено: + Получено: + Чрез: В очакване Изпрати на @@ -2821,6 +2827,7 @@ Промени Номер + Промени номер Редакция на номер @@ -3021,7 +3028,13 @@ SMS . %1$s + Добави съобщение + + Съобщението е изпратено + Съобщенията са изпратени + + Добави съобщение Отказ Мъгли Върни diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 348bd23b5e..78ae2c27fd 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -3,14 +3,14 @@ Ja Nej Slet - Vent venligst… + Vent venligst … Gem Egen note Ny besked - Molly opdaterer… + Molly opdaterer … For øjeblikket: %s Du har endnu ikke indtastet en adgangssætning! @@ -18,7 +18,7 @@ Dette låser permanent op for Molly og besked-notifikationer. Deaktiver Afregistrering - Afregistrerer fra Molly-beskeder og opkald… + Afregistrerer fra Molly-beskeder og opkald … Deaktiver Molly-beskeder og opkald? Deaktiver Molly-beskeder og opkald ved at afregistrere fra serveren. Det er nødvendigt at registrere telefonnummeret igen for at bruge det fremadrettet. Fejl ved oprettelse af forbindelse til serveren @@ -68,9 +68,9 @@ Molly kræver tilladelse til at tilgå din placering, for at kunne vedhæfte placeringer, hvilket er blevet nægtet. Gå venligst til appindstillinger, vælg \"Tilladelser\" og tilvælg \"Placering\". Uploader mediefil… - Komprimerer video… + Komprimerer video … - Tjekker efter beskeder… + Tjekker efter beskeder … Blokerede brugere Tilføj blokeret bruger @@ -179,7 +179,7 @@ fra %s til %s   Læs mere -   Download mere +   Hent mere   Afventer Denne besked blev slettet. Du slettede denne besked. @@ -271,12 +271,12 @@ Gemmer vedhæftning… Gemmer %1$d vedhæftninger i lagerplads… - Sender… + Afventer … Data (Signal) MMS SMS Sletter - Sletter beskeder… + Sletter beskeder … Slet for mig Slet for alle Denne besked slettes for alle i samtalen, hvis de har den seneste version af Signal. De kan se at du har slettet en besked. @@ -308,7 +308,7 @@ Dette sletter %1$d valgte samtaler permanent Sletter - Sletter valgte samtaler… + Sletter valgte samtaler … Samtale arkiveret %d samtaler arkiveret @@ -409,8 +409,8 @@ Deaktiver For at gendanne fra sikkerhedskopi skal du installere Molly igen. Åbn appen og tryk på \"Gendan sikkerhedskopi.\" Vælg derefter en sikkerhedskopifil. %1$s Læs mere - I gang… - %1$d indtil videre… + I gang … + %1$d indtil videre … %1$s%% indtil videre… Molly kræver tilladelse til at tilgå ekstern lagerplads for at oprette sikkerhedskopier, hvilket er blevet nægtet. Gå venligst til appindstillinger, vælg \"Tilladelser\" og tilvælg \"Lagerplads\". @@ -438,7 +438,7 @@ Enheden vil ikke længere være i stand til at sende og modtage beskeder, hvis den frakobles. Forbindelse til netværk fejlede Prøv igen - Fjerner forbindelse til enhed… + Fjerner forbindelse til enhed … Frakobler enhed Netværksfejl! @@ -687,7 +687,7 @@ Dette er en usikker MMS-gruppe. Inviter dine kontakter til Signal for at skrive og tale privat. Inviter nu mere - Tilføj gruppebeskrivelse… + Tilføj gruppebeskrivelse … Underret mig ved omtaler Vil du modtage notifikationer, når du omtales i ignorerede samtaler? @@ -789,9 +789,9 @@ Del Del med kontakter - Del via… + Del via … Afbryd - Sender… + Sender … Invitationer sendt! Inviter til Molly Send SMS (%d) @@ -827,9 +827,9 @@ Dette vil slette alle %1$d markerede filer permanent. Alle beskedtekster associeret med disse filer vil også blive slettet Sletter - Sletter beskeder… + Sletter beskeder … Vælg alle - Samler vedhæftninger… + Samler vedhæftninger … Sortér efter Nyeste Ældste @@ -889,13 +889,13 @@ Ikke nu Multimediebesked - Downloader MMS… - MMS-besked kunne ikke downloades, tryk for at prøve igen + Henter MMS-besked … + MMS-besked kunne ikke hentes, tryk for at prøve igen Send til %s Åbn kamera - Tilføj en billedtekst… + Tilføj en billedtekst … Et element blev fjernet, fordi det oversteg max. grænsen Et element blev fjernet, fordi det havde en ukendt type Et element blev fjernet, fordi det overskred størrelsesgrænsen eller havde en ukendt type @@ -903,7 +903,7 @@ Besked til %s Besked Vælg modtagere - Molly kræver tilladelse til at tilgå dine kontakter for at kunne vise dem. + Molly har brug for tilladelse til at tilgå dine kontakter for at kunne vise dem. Molly kræver tilladelse til at tilgå dine kontakter, hvilket er blevet nægtet. Gå venligst til appindstillinger, vælg \"Tilladelser\" og tilvælg \"Kontakter\". Du kan ikke dele flere end %d fil. @@ -1149,7 +1149,7 @@ \n• Send beskeder i dit navn Tilføjer enhed - Forbinder ny enhed… + Forbinder ny enhed … Enhed godkendt! Ingen enhed fundet Netværksfejl. @@ -1203,7 +1203,7 @@ Opret ny pinkode Advarsel - Hvis du deaktiverer pinkode mister du alle data, når du omregistrerer Signal, medmindre du sikkerhedskopierer og gendanner manuelt. Du kan ikke aktivere registreringslås, mens pinkoden er deaktiveret. + Hvis du deaktiverer pinkode mister du alle data, når du omregistrerer Signal. Medmindre du sikkerhedskopierer og gendanner manuelt. Du kan ikke aktivere registreringslås, mens pinkoden er deaktiveret. Deaktiver pinkode Bedøm appen @@ -1229,9 +1229,9 @@ Unavngivet gruppe - Besvarer… - Afslutter opkald… - Ringer… + Besvarer … + Afslutter opkald … + Ringer … Optaget Modtager utilgængelig Netværksfejl! @@ -1242,7 +1242,7 @@ Tryk her for at starte video For at ringe til %1$s kræver Molly tilladelse til at tilgå din kamera Molly %1$s - Ringer op… + Ringer op … Gruppe er for stor til at ringe til alle deltagerne. Signal-opkald @@ -1253,8 +1253,8 @@ Det maksimale antal på %1$d deltagere er nået for dette opkald. Prøv igen senere. Vis deltagere Din video er slået fra - Genopretter forbindelse… - Forbinder… + Genopretter forbindelse … + Forbinder … Afbrudt Signal vil ringe til %1$s Signal vil ringe til %1$s og %2$s @@ -1514,7 +1514,7 @@ nummer (%s) er ugyldigt Din kontakt bruger en gammel version af Signal. Bed dem om at opdatere før verifikation af dit sikkerhedsnummer. Din kontakt bruger en nyere version af Signal med en inkompatibel QR-kode. Opdatér venligst for at sammenligne. Den skannede QR-kode er ikke korrekt formateret som verifikationskode for sikkerhedsnummer. Prøv at skanne igen. - Del sikkerhedsnummer via… + Del sikkerhedsnummer via … Vores Signal-sikkerhedsnummer: Det lader til at du ikke har nogen apps at dele til. Sikkerhedsnummer til sammenligning findes ikke i udklipsholderen @@ -1531,7 +1531,7 @@ nummer (%s) er ugyldigt Slå notifikationer fra - Importerer… + Importerer … Importerer tekstbeskeder Import afsluttet Import af systemdatabase er afsluttet @@ -1657,7 +1657,7 @@ nummer (%s) er ugyldigt Vis telefontaster Ingen kontakter. - Indlæser kontakter… + Indlæser kontakter … Kontaktbillede @@ -1725,7 +1725,7 @@ nummer (%s) er ugyldigt Udløbstiden for forsvindende besked indstilles til %1$s, når du sender beskeder til dem. Afspil … Pause - Download + Hent Lyd Video @@ -1768,7 +1768,7 @@ nummer (%s) er ugyldigt For at modtage opkaldsnotifikationer, tryk på Indstillinger og aktiver notifikationer og sørg for, at lyd og pop-op er aktiveret. For at modtage opkaldsnotifikationer, tryk på Indstillinger og aktiver baggrundsaktivitet i \"Batteri\"-indstillinger. - Indlæser lande… + Indlæser lande … Søg Ingen matchende lande @@ -1864,7 +1864,7 @@ nummer (%s) er ugyldigt Gruppebeskrivelser vil være synlige for medlemmer af denne gruppe og personer, der er blevet inviteret. Om - Skriv et par ord om dig selv… + Skriv et par ord om dig selv … %1$d/%2$d Tal frit Krypteret @@ -1892,7 +1892,7 @@ nummer (%s) er ugyldigt Tryk for at skanne Vellykket match Kunne ikke verificere sikkerhedsnummer - Henter… + Indlæser … Markér som verificeret Fjern verifikation @@ -2084,7 +2084,7 @@ nummer (%s) er ugyldigt Samtalefarve & baggrund Deaktiver pinkode Aktiver pinkode - Hvis du deaktiverer pinkoden mister du alle data når du omregistrerer Signal, medmindre du sikkerhedskopierer og gendanner manuelt. Du kan ikke aktivere registreringslås, mens pinkoden er deaktiveret. + Hvis du deaktiverer pinkoden mister du alle data, når du omregistrerer Signal. Medmindre du sikkerhedskopierer og gendanner manuelt. Du kan ikke aktivere registreringslås, mens pinkoden er deaktiveret. Pinkoder sørger for at information lagret hos Signal er krypteret, så kun du kan få adgang til den. Din profil, indstillinger og kontakter gendannes når du geninstallerer. Du har ikke brug for din pinkode for at åbne appen. Systemstandard Sprog @@ -2106,7 +2106,7 @@ nummer (%s) er ugyldigt Ved anvendelse af mobildata Ved brug af WiFi Ved roaming - Automatisk download af mediefiler + Automatisk hentning af mediefiler Beskedhistorik Brug af lagerplads Billeder @@ -2133,7 +2133,7 @@ nummer (%s) er ugyldigt Deaktiver Signals indbyggede emoji-understøttelse Videresend alle opkald gennem Signal-serveren, for at undgå at afsløre din IP-adresse over for din kontakt. Opkaldskvaliteten vil blive forringet. Videresend altid opkald - Hvem kan… + Hvem kan … App-adgang Kommunikation Samtaler @@ -2188,7 +2188,7 @@ nummer (%s) er ugyldigt Nu introduceres betalinger (Beta) Brug Molly til at sende og modtage MobileCoin, en ny privatlivsfokuseret digital valuta. Aktivér for at komme i gang. Aktiver Betalinger - Aktiverer betalinger… + Aktiverer betalinger … Gendan betalingskonto Ingen nylig aktivitet endnu Afventende anmodninger @@ -2233,8 +2233,8 @@ nummer (%s) er ugyldigt Detaljer Status - Indsender betaling… - Bearbejder betaling… + Indsender betaling … + Bearbejder betaling … Betaling gennemført Betaling fejlede Netværksgebyr @@ -2298,8 +2298,8 @@ nummer (%s) er ugyldigt Til Samlet beløb Saldo: %1$s - Indsender betaling… - Bearbejder betaling… + Indsender betaling … + Bearbejder betaling … Betaling gennemført Betaling fejlede Behandling af betaling vil fortsætte @@ -2315,7 +2315,7 @@ nummer (%s) er ugyldigt - Ny besked til… + Ny besked til … Blokér bruger Føj til gruppe @@ -2484,7 +2484,7 @@ nummer (%s) er ugyldigt Din pinkode blev ikke gemt. Vi vil minde dig om at oprette en pinkode senere. Pinkode oprettet. Indtast din pinkode igen - Opretter pinkode… + Opretter pinkode … Nu introduceres pinkoder Pinkoder sørger for at information opbevaret hos Signal er krypteret, så kun du har adgang til den. Din profil, indstillinger og kontakter vil blive gendannet når du geninstallerer. Du behøver ikke din pinkode for at åbne appen. @@ -2547,8 +2547,8 @@ nummer (%s) er ugyldigt Opret pinkode Transport-ikon - Henter… - Forbinder… + Indlæser … + Forbinder … Tilladelse påkrævet Signal kræver tilladelse til at tilgå dine SMS\'er for at kunne sende SMS\'er, hvilket er blevet nægtet. Gå venligst til appindstillinger, vælg \"Tilladelser\" og tilvælg \"SMS\". Fortsæt @@ -2574,8 +2574,8 @@ nummer (%s) er ugyldigt Gendan Kan ikke importere sikkerhedskopier fra nyere versioner af Signal Forkert adgangssætning til sikkerhedskopi - Tjekker… - %d beskeder indtil videre… + Tjekker … + %d beskeder indtil videre … Gendan fra sikkerhedskopi? Gendan dine beskeder og mediefiler fra en lokal sikkerhedskopi. Hvis du ikke gendanner nu kan det ikke gøres senere. Størrelse på sikkerhedskopi: %s @@ -2594,7 +2594,7 @@ nummer (%s) er ugyldigt Verificér Du indtastede den korrekte adgangssætning til sikkerhedskopi Forkert adgangssætning - Opretter Molly-sikkerhedskopi… + Opretter Molly-sikkerhedskopi … Sikkerhedskopiering mislykkedes Din mappe med sikkerhedskopier er blevet slettet eller flyttet. Din sikkerhedskopifil er for stor til at kunne gemmes på denne lagerenhed. @@ -2662,9 +2662,9 @@ nummer (%s) er ugyldigt 3. Tryk på \"Overfør konto\" og derefter \"Fortsæt\" på begge enheder - Forbereder forbindelse til den gamle Android-enhed… + Forbereder forbindelse til den gamle Android-enhed … Tager et øjeblik. Vil snart være klar - Venter på, at den gamle Android-enhed forbinder… + Venter på, at den gamle Android-enhed forbinder … Molly kræver placeringstilladelsen for at opspore og forbinde din gamle Android-enhed. Molly kræver placeringstjenester aktiveret for at opspore og forbinde med din gamle Android-enhed. Molly kræver Wi-Fi til for at opspore og forbinde med din gamle Android-enhed. Wi-Fi skal være tændt, men det behøver ikke at være forbundet til et Wi-Fi-netværk. @@ -2672,7 +2672,7 @@ nummer (%s) er ugyldigt Gendan en sikkerhedskopi Der opstod en uventet fejl under forbindelsesforsøg til din gamle Android-enhed. - Søger efter ny Android-enhed… + Søger efter ny Android-enhed … Molly kræver placeringstilladelsen for at opspore og forbinde med din nye Android-enhed. Molly kræver placeringstjenester aktiveret for at opspore og forbinde med din nye Android-enhed. Molly kræver Wi-Fi til for at opspore og forbinde med din nye Android-enhed. Wi-Fi skal være tændt, men det behøver ikke at være forbundet til et Wi-Fi-netværk. @@ -2709,13 +2709,13 @@ nummer (%s) er ugyldigt Prøv igen Venter på anden enhed Tryk på Fortsæt på din anden enhed for at starte overførslen. - Tryk på Fortsæt på din anden enhed… + Tryk på Fortsæt på din anden enhed … Der kan ikke overføres fra en nyere version af Signal Overfører data Hold begge enheder tæt ved hinanden. Sluk ikke for enhederne, og hold Molly åbent. Overførsler er end-to-end krypteret - %1$d beskeder indtil videre… + %1$d beskeder indtil videre … %1$s%% af beskeder indtil videre… Annullér @@ -2747,12 +2747,12 @@ nummer (%s) er ugyldigt Fortsæt registreringen Kontooverførsel - Forbereder forbindelse til den anden Android-enhed… - Forbereder forbindelse til den anden Android-enhed… - Søger efter din anden Android-enhed… - Forbinder til den anden Android-enhed… + Forbereder forbindelse til den anden Android-enhed … + Forbereder forbindelse til den anden Android-enhed … + Søger efter din anden Android-enhed … + Forbinder til den anden Android-enhed … Bekræftelse er påkrævet - Overfører kontoen… + Overfører konto … Gennemfør registreringen på din nye enhed Din Signal-konto er blevet overført til din nye enhed, men du skal gennemføre registreringen på den for at fortsætte. Signal vil være inaktivt på denne enhed. @@ -2868,9 +2868,9 @@ nummer (%s) er ugyldigt Lokale data kunne ikke slettes. Du kan rydde det manuelt i systemets applikationsindstillinger. Åbn appindstillinger - Forlader grupper… + Forlader grupper … - Sletter konto… + Sletter konto … Afhængigt af antallet af grupper, du er med i, kan dette tage et par minutter @@ -3069,7 +3069,7 @@ nummer (%s) er ugyldigt Opdatér pinkode Behold gammel pinkode? - Det ser ud til, at du forsøgte at ændre dit nummer, men vi kunne ikke fastslå, om det lykkedes.\n\nTjekker igen nu… + Det ser ud til, at du forsøgte at ændre dit nummer, men vi kunne ikke fastslå, om det lykkedes.\n\nTjekker igen nu … Ændringsstatus bekræftet Dit nummer er blevet bekræftet som %1$s. Hvis dette ikke er dit nye nummer, bedes du genstarte processen med at ændre nummer igen. Ændringsstatus ubekræftet @@ -3085,7 +3085,7 @@ nummer (%s) er ugyldigt Beskeder Opkald - Underret når… + Underret når … Kontakt begynder at bruge Signal Notifikationsprofiler @@ -3410,7 +3410,7 @@ nummer (%s) er ugyldigt Tilføj et Signal Boost %1$s/måned Fornyes %1$s - Behandler transaktion… + Behandler transaktion … Kunne ikke tilføje emblem. %1$s Kontakt venligst support. @@ -3429,7 +3429,7 @@ nummer (%s) er ugyldigt Kontakt venligst support for at få mere information. Kontakt Support Opnå ret til et %1$s-emblem - Bearbejder betaling… + Bearbejder betaling … Fejl ved behandling af betaling diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 3fdedb7162..38c6e4a215 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -1980,7 +1980,7 @@ Se recibió un mensaje de intercambio de claves para una versión no válida del Durante 8 horas Durante un día Durante 7 días - Siempre + Para siempre Ajustes predeterminados Activado Desactivado diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 04189488c3..6ad5f36175 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -332,15 +332,15 @@ برداشتن سنجاق - برداشتن سنجاق + حذف سنجاق بستن - بستن + بی‌صدا باز کردن - باز کردن + صدادار انتخاب @@ -361,9 +361,9 @@ %d انتخاب شد - پروفایل اعلان + پروفایل اعلانات - پروفایل اعلان خود را اینجا خاموش یا روشن کنید. + پروفایل اعلانات خود را اینجا روشن یا خاموش کنید. %1$s روشن @@ -409,7 +409,7 @@ خاموش برای بازگردانی یک پشتیبان، یک نسخهٔ جدید سیگنال را نصب کنید. برنامه را باز کرده و روی «بازگردانی پشتیبان» ضربه بزنید، سپس فایل پشتیبان را مشخص کنید. %1$s بیشتر یاد بگیرید - در حال پیشرفت… + در حال پیشروی… %1$d تا بحال… %1$s%% تا کنون… @@ -495,7 +495,7 @@ دعوت‌نامه ارسال شد - %d دعوت‌نامه ارسال شدند + %d دعوت‌نامه ها ارسال شدند «%1$s» نمی‌تواند به صورت خودکار توسط شما به این گروه اضافه شود.\n\nآن‌ها برای پیوستن دعوت شده‌اند و پیام‌های گروهی را تا زمانی که دعوت را نپذیرند، نخواهند دید. این کاربران نمی‌توانند به صورت خودکار توسط شما به این گروه اضافه شوند.\n\nآن‌ها برای پیوستن به گروه دعوت شده‌اند و پیام‌های گروهی را تا زمانی که دعوت را بپذیرند، نخواهند دید. diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 0908045b9a..26dcf50e47 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -3243,9 +3243,11 @@ broj telefona Najčešća pitanja Donirajte jednokratnom uplatom Unesi prilagođeni iznos + Dodaj Signal Boost Molimo Vas da kontaktirate podršku Postanite pretplatnik + Dodaj Boost Ne sada Kontaktirajte podršku Obrada uplate… diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index 2d6d5099d6..4c29845f75 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -395,7 +395,7 @@ מחק מחק - בחר הכל + בחר הכול %d נבחר %d נבחרו @@ -747,7 +747,7 @@ כבוי מופעל הצג את כל חברי הקבוצה - ראה הכל + ראה הכול חבר קבוצה %d התווסף. %d חברי קבוצה התווספו. @@ -913,7 +913,7 @@ מדיה קבצים שמע - הכל + הכול למחוק פריט נבחר? למחוק פריטים נבחרים? @@ -928,7 +928,7 @@ מוחק מוחק הודעות… - בחר הכל + בחר הכול אוסף צרופות… מיין לפי החדש ביותר @@ -938,7 +938,7 @@ תצוגת סורג תצוגת רשימה נבחר - בחר הכל + בחר הכול שמור שמור @@ -1482,7 +1482,7 @@ קוד וידוא יישלח אל: תקבל שיחה כדי לוודא מספר זה. האם מספר הטלפון שלך למעלה נכון? - עריכת המספר + ערוך מספר שירותי Google Play חסרים במכשיר זה חסרים שירותי Google Play. אתה יכול עדין להשתמש ב־Molly, אבל תצורה זו עשויה לגרום לאמינות מופחתת או לביצועים מופחתים.\n\nאם אינך משתמש מתקדם, אינך מריץ ROM של Android שהותקן לאחר רכישה, או שאתה מאמין שאתה רואה זאת בטעות, אנא צור קשר עם support@molly.im לעזרה באיתור תקלות. אני מבין @@ -1712,7 +1712,7 @@ שגיאה במסירת הודעה. מסירת הודעה הושהתה. וודא כדי להמשיך להתכתב ב־Molly. - סמן שהכל נקרא + סמן הכול כנקרא סמן כנקרא כבה התראות אלו תמונה לצפייה חד־פעמית @@ -2064,7 +2064,7 @@ למד עוד.]]> הקש כדי לסרוק התאמה מוצלחת - נכשל בוידוא מספר ביטחון + נכשל בווידוא מספר ביטחון טוען… סמן כמוֻדא נקה וידוא @@ -2296,7 +2296,7 @@ זה ימחק לצמיתות את כל היסטורית ההודעות והמדיה מהמכשיר שלך. האם אתה בטוח שאתה רוצה למחוק את כל היסטורית ההודעות? כל היסטורית ההודעות תימחק לצמיתות. פעולה זו אינה ניתנת לביטול. - מחק הכל כעת + מחק הכול עכשיו לנצח שנה 1 6 חודשים @@ -2357,7 +2357,7 @@ כל הפעילות - הכל + הכול נשלח התקבל היכרות עם תשלומים (בטא) @@ -2368,7 +2368,7 @@ אין פעילות מהזמן האחרון עדיין בקשות ממתינות פעילות מהזמן האחרון - ראה הכל + ראה הכול הוסף כספים שלח %1$s נשלחו @@ -2546,7 +2546,7 @@ מחק נבחרים הצמד נבחרים בטל הצמדת נבחרים - בחר הכל + בחר הכול אחסן בארכיון נבחרים הוצא מארכיון נבחרים סמן כנקרא @@ -2590,7 +2590,7 @@ קבוצה חדשה הגדרות נעל - סמן הכל כנקרא + סמן הכול כנקרא הזמן חברים העתק ללוח העריכה @@ -2690,7 +2690,7 @@ למד עוד הכנס את ה־PIN שלך - הכנס את ה־PIN שיצרת עבור החשבון שלך. הוא שונה מקוד הוידוא שלך במסרון. + הכנס את ה־PIN שיצרת עבור החשבון שלך. הוא שונה מקוד הווידוא שלך במסרון. הכנס PIN אלפאנומרי הכנס PIN מספרי PIN שגוי. נסה שוב. @@ -3239,24 +3239,24 @@ המשך מספר הטלפון שלך השתנה. - שינוי המספר + שנה מספר המספר הישן שלך - מספר טלפון ישן + מספר ישן של טלפון המספר החדש שלך - מספר טלפון חדש + מספר חדש של טלפון מספר הטלפון שהכנסת אינו תואם אל מספר הטלפון של החשבון שלך. אתה חייב לציין את קוד המדינה של המספר הישן שלך אתה חייב לציין את מספר הטלפון הישן שלך אתה חייב לציין את קוד המדינה של המספר החדש שלך אתה חייב לציין את מספר הטלפון החדש שלך - שינוי המספר + שנה מספר מוודא את %1$s אתגר מענה דרוש שנה מספר אתה עומד לשנות את מספר הטלפון שלך מן %1$s אל %2$s.\n\nלפני המשכה, אנא וודא שהמספר למטה נכון. - עריכת המספר + ערוך מספר שינוי מספר של Signal - צריך עזרה עם PIN עבור Android (v2 PIN) @@ -3339,7 +3339,7 @@ איכות מדיה הגדר איכות מדיה שליחת מדיה באיכות גבוהה תשתמש ביותר נתונים. - גבוה + גבוהה תקנית שיחות @@ -3433,7 +3433,7 @@ שחרר חסימה שחרר חסימת קבוצה הוסף אל קבוצה - ראה הכל + ראה הכול הוסף חברי קבוצה הרשאות בקשות והזמנות @@ -3729,7 +3729,7 @@ תזמן - ראה הכל + ראה הכול הוסף תזמון diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 421f84e7f1..7e72628a5e 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -413,7 +413,7 @@ Archyvuoti pokalbiai (%d) - Patvirtinta(-s) + Patvirtinta(s) Jūs +%1$d @@ -1913,7 +1913,7 @@ Išeiti iš skambučio Gali būti, kad šie žmonės iš naujo įdiegė programėlę ar pakeitė įrenginį. Patvirtinkite savo saugumo numerį su kiekvienu iš jų, kad užtikrintumėte privatumą. Rodyti - Anksčiau patvirtinta(-s) + Anksčiau patvirtinta(s) Pranešimai apie skambučius įjungti. Įjungti pranešimus apie skambučius diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index cd92affca7..a4390a6a81 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -5,7 +5,7 @@ Избриши Ве молиме почекајте… Зачувај - Забелешка за себе + Белешка за себе Нова порака @@ -1084,7 +1084,7 @@ Го обележавте сигурносниот број со %s како непроверен Го обележавте Вашиот сигурносен број со %s како непроверен од друг уред. Порака од %s не може да биде испорачана - %1$s го смени бројот во нов број. + %1$s го смени бројот со нов број. %1$s започна групен повик · %2$s %1$s е во групниот повик · %2$s @@ -1940,7 +1940,7 @@ Вклучи запис за дебагирање. Што е ова? Како се чувствувате? (Опционално) - Кажете ни зошто не контактирате. + Кажете ни зошто нѐ контактирате. Информации за поддршка Барање за поддршка за Signal Android Запис за дебагирање: @@ -2283,7 +2283,7 @@ 0 < Backspace - Додај порака + Додај белешка Конверзиите се само проценки и можеби не се точни. Белешка diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index 2b5edd5935..610f67515a 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -298,6 +298,7 @@ %s-മായുള്ള നിങ്ങളുടെ സുരക്ഷാ നമ്പർ മാറി. %s -മായുള്ള നിങ്ങളുടെ സുരക്ഷാ നമ്പർ മാറി. അവർ Signal വീണ്ടും ഇൻസ്റ്റാൾ ചെയ്തതുകൊണ്ടോ ഡിവൈസ് മാറിയതുകൊണ്ടോ ആവാമിത്. പുതിയ സുരക്ഷാ നമ്പർ ഉറപ്പുവരുത്താൻ \'ഉറപ്പാക്കു\' ടാപ് ചെയ്യുക. ഇത് ഓപ്ഷണൽ ആണ്. + %1$sസജീവം തിരഞ്ഞെടുത്ത സംഭാഷണം ഇല്ലാതാക്കണോ? തിരഞ്ഞെടുത്ത സംഭാഷണങ്ങൾ ഇല്ലാതാക്കണോ? @@ -317,12 +318,54 @@ സംഭാഷണം ഇൻ‌ബോക്സിലേക്ക് നീക്കി %d സംഭാഷണങ്ങൾ ഇൻ‌ബോക്സിലേക്ക് നീക്കി + + വായിച്ചു + വായിച്ചു + + + വായിച്ചില്ല + വായിച്ചില്ല + + + പിൻ + പിൻ + + + അൺപിൻ ചെയ്യുക + അൺപിൻ ചെയ്യുക + + + നിശബ്ദമാക്കുക + നിശബ്ദമാക്കുക + + + ശബ്‌ദിപ്പിക്കുക + ശബ്‌ദിപ്പിക്കുക + തിരഞ്ഞെടുക്കൂ + + ആർക്കൈവ് + ആർക്കൈവ് + + + അൺആർക്കൈവ് + അൺആർക്കൈവ് + + + ഇല്ലാതാക്കുക + ഇല്ലാതാക്കുക + എല്ലാം തിരഞ്ഞെടുക്കുക + + %d തിരഞ്ഞെടുത്തു + %d തിരഞ്ഞെടുത്തു + + അറിയിപ്പ് രൂപരേഖ നിങ്ങൾക്കുള്ള അറിയിപ്പുകൾ ഇവിടെ ഓണാക്കുക അല്ലെങ്കിൽ ഓഫ് ചെയ്യുക. + %1$sസജീവം കീ കൈമാറ്റ സന്ദേശം @@ -369,6 +412,7 @@ പുരോഗതിയിൽ… %1$d ഇതുവരെ…. + %1$s%% ഇതുവരെ… ബാക്കപ്പുകൾ സൃഷ്ടിക്കുന്നതിന് Molly-ന് ബാഹ്യ സ്റ്റോറജ് ​​അനുമതി ആവശ്യമാണ്, പക്ഷേ ഇത് ശാശ്വതമായി നിരസിക്കപ്പെട്ടു. അപ്ലിക്കേഷൻ ക്രമീകരണങ്ങളിലേക്കു പോയി , \"അനുമതികൾ\" തിരഞ്ഞെടുത്ത് \"സ്റ്റോറജ്\" ഉപയോഗാനുമതി നൽകുക . Custom ഉപയോഗിക്കുന്നു: %s @@ -745,10 +789,12 @@ പങ്കിടുക കോൺടാക്റ്റുകളുമായി പങ്കിടുക + ഇതുവഴി പങ്കിടുക… റദ്ദാക്കുക അയയ്ക്കുന്നു… ക്ഷണങ്ങൾ അയച്ചു! Molly-ലേക്ക് ക്ഷണിക്കുക + (%d) SMS അയയ്ക്കുക %d SMS ക്ഷണം അയയ്‌ക്കണോ? %d SMS ക്ഷണങ്ങൾ അയയ്‌ക്കണോ? @@ -793,6 +839,18 @@ ലിസ്റ്റ് കാഴ്ച തിരഞ്ഞെടുത്തു എല്ലാം തിരഞ്ഞെടുക്കുക + + സൂക്ഷിക്കുക + സൂക്ഷിക്കുക + + + ഇല്ലാതാക്കുക + ഇല്ലാതാക്കുക + + + %1$d തിരഞ്ഞെടുത്തു (%2$s) + %1$d തിരഞ്ഞെടുത്തു (%2$s) + ഫയൽ ഓഡിയോ വീഡിയോ @@ -817,7 +875,9 @@ Signal കോൾ പുരോഗതിയിലാണ് Signal കോൾ സ്ഥാപിക്കുന്നു ഇൻകമിംഗ് സിഗ്നൽ കോൾ + Signal ഗ്രൂപ്പ് കോള്‍ വരുന്നു Signal കോൾ സേവനം നിർത്തുന്നു + കോൾ നിരസിക്കുക കോൾ സ്വീകരിക്കുക കോൾ അവസാനിപ്പിക്കുക കോൾ റദ്ദാക്കുക @@ -1024,6 +1084,7 @@ %s-മായുള്ള നിങ്ങളുടെ സുരക്ഷാ നമ്പർ നിങ്ങൾ സ്ഥിരീകരിച്ചിട്ടില്ലതായി അടയാളപ്പെടുത്തി %s-മായുള്ള നിങ്ങളുടെ സുരക്ഷാ നമ്പർ നിങ്ങൾ മറ്റൊരു ഉപകരണത്തിൽ നിന്ന് സ്ഥിരീകരിച്ചിട്ടില്ലതായി അടയാളപ്പെടുത്തി %s-ൽ നിന്നുള്ള ഒരു സന്ദേശം ഡെലിവർ ചെയ്യാൻ കഴിഞ്ഞില്ല + %1$s അവരുടെ നമ്പർ ഒരു പുതിയ നമ്പറിലേക്ക് മാറ്റി. %1$s ഒരു ഗ്രൂപ്പ് കോൾ ആരംഭിച്ചു . %2$s %1$s ഗ്രൂപ്പ് കോളിലാണ് · %2$s @@ -1181,6 +1242,7 @@ %1$s-യെ വിളിക്കാൻ, Molly-ന് നിങ്ങളുടെ ക്യാമറയിലേക്ക് ആക്സസ് ആവശ്യമാണ് Molly %1$s വിളിക്കുന്നു… + പങ്കെടുക്കുന്നവരെ വിളിക്കാൻ കഴിയാത്തത്ര വലുതാണ് ഗ്രൂപ്പ്. Signal വിളി Signal വീഡിയോ കോൾ @@ -1193,18 +1255,45 @@ റീകണക്ടിംഗ്… ചേരുന്നു… വിച്ഛേദിച്ചു + Signal %1$s നെ വിളിക്കും + Signal %1$s നെയും %2$s നെയും വിളിക്കും + + Signal %1$s, %2$s നെയും മറ്റ് %3$d പേരെയും വിളിക്കും + Signal %1$s, %2$s നെയും മറ്റ് %3$d പേരെയും വിളിക്കും + + %1$s യെ അറിയിക്കും + %1$s യെയും %2$s യെയും അറിയിക്കും + + %1$s, %2$s യെയും മറ്റ് %3$d പേരെയും അറിയിക്കും + %1$s, %2$s യെയും മറ്റ് %3$d പേരെയും അറിയിക്കും + + %1$s നെ വിളിയ്ക്കുന്നു + %1$s നെയും %2$s നെയും വിളിയ്ക്കുന്നു + + %1$s, %2$s നെയും മറ്റ് %3$d പേരെയും വിളിക്കുന്നു + %1$s, %2$s നെയും മറ്റ് %3$d പേരെയും വിളിക്കുന്നു + %1$sനിങ്ങളെ വിളിക്കുന്നു + %1$s നിങ്ങളെയും %2$s-നെയും വിളിക്കുന്നു + %1$s നിങ്ങളെയും %2$s-നെയും %3$s-നെയും വിളിക്കുന്നു + + %1$s നിങ്ങളെയും %2$s-നെയും %3$s-നെയും മറ്റ് %4$d പേരെയും വിളിക്കുന്നു + %1$s നിങ്ങളെയും %2$s-നെയും %3$s-നെയും മറ്റ് %4$d പേരെയും വിളിക്കുന്നു + മറ്റാരുമില്ല %1$s ഈ കോളിലുണ്ട് + %1$s പേർ ഈ കോളിലുണ്ട് %1$s - ഉം %2$s - ഉം ഈ കോളിലുണ്ട്  %1$sഅവതരിപ്പിക്കുന്നു %1$s- ഉം, %2$s - ഉം പിന്നെ %3$d - ഉം ഈ കോളിലുണ്ട് %1$s - ഉം, %2$s - ഉം പിന്നെ %3$d ആളുകളും ഈ കോളിലുണ്ട് + തിരിക്കൂ സ്പീക്കർ ക്യാമറ നിശബ്ദമാക്കുക + റിംഗ് കോൾ അവസാനിപ്പിക്കൂ @@ -1246,8 +1335,13 @@ Play Services പിശക് Google Play Services അപ്‌ഡേറ്റുചെയ്യുന്നു അല്ലെങ്കിൽ താൽക്കാലികമായി ലഭ്യമല്ല. ദയവായി വീണ്ടും ശ്രമിക്കുക. നിബന്ധനകളും സ്വകാര്യതാ നയവും + സുഹൃത്തുക്കളുമായി ബന്ധപ്പെടാനും സന്ദേശങ്ങള്‍ അയക്കാനും നിങ്ങളെ സഹായിക്കുന്നതിന് Signal-ന് കോൺ‌ടാക്റ്റുകൾ, മീഡിയ അനുമതികൾ ആവശ്യമാണ്. Signal-ന്റെ സ്വകാര്യ കോൺടാക്റ്റ് കണ്ടെത്തൽ ഉപയോഗിച്ചാണ് നിങ്ങളുടെ കോൺ‌ടാക്റ്റുകൾ അപ്‌ലോഡ് ചെയ്‌തിരിക്കുന്നത്, അതിനർത്ഥം അവ എൻഡ്-ടു-എൻഡ് എൻക്രിപ്റ്റ് ചെയ്‌തതും Signal സേവനത്തിന് ഒരിക്കലും ദൃശ്യമാകാത്തതുമാണ്. + സുഹൃത്തുക്കളുമായി ബന്ധപ്പെടാൻ നിങ്ങളെ സഹായിക്കുന്നതിന് Signal-ന് കോൺ‌ടാക്റ്റുകൾ അനുമതി ആവശ്യമാണ്. Signal-ന്റെ സ്വകാര്യ കോൺടാക്റ്റ് കണ്ടെത്തൽ ഉപയോഗിച്ചാണ് നിങ്ങളുടെ കോൺ‌ടാക്റ്റുകൾ അപ്‌ലോഡ് ചെയ്‌തിരിക്കുന്നത്, അതിനർത്ഥം അവ എൻഡ്-ടു-എൻഡ് എൻക്രിപ്റ്റ് ചെയ്‌തതും Signal സേവനത്തിന് ഒരിക്കലും ദൃശ്യമാകാത്തതുമാണ്. ഈ നമ്പർ രജിസ്റ്റർ ചെയ്യുന്നതിന് നിങ്ങൾ വളരെയധികം ശ്രമങ്ങൾ നടത്തി. പിന്നീട് വീണ്ടും ശ്രമിക്കുക. സേവനത്തിലേക്ക് കണക്റ്റുചെയ്യാനായില്ല. നെറ്റ്‌വർക്ക് കണക്ഷൻ പരിശോധിച്ച് വീണ്ടും ശ്രമിക്കുക. + അംഗീകൃതമല്ലാത്ത നമ്പര്‍ രൂപം + നിങ്ങൾ നൽകിയ നമ്പർ (%1$s) അംഗീകൃതമല്ലാത്ത നമ്പര്‍ രൂപത്തില്‍ ആണെന്ന് തോന്നുന്നു.\n\nനിങ്ങൾ ഉദ്ദേശിച്ചത് %2$s എന്നാണോ? + Molly ആൻഡ്രോയിഡ് - ഫോണ്‍ നമ്പര്‍ രുപം ഒരു ഡീബഗ് ലോഗ് സമർപ്പിക്കുന്നതിൽ നിന്ന് നിങ്ങൾ ഇപ്പോൾ %d പടി അകലെയാണ്. ഒരു ഡീബഗ് ലോഗ് സമർപ്പിക്കുന്നതിൽ നിന്ന് നിങ്ങൾ ഇപ്പോൾ %d ഘട്ടങ്ങൾ അകലെയാണ്. @@ -1520,6 +1614,7 @@ ഉപകരണം മേലിൽ രജിസ്റ്റർ ചെയ്തിട്ടില്ല നിങ്ങളുടെ ഫോൺ നമ്പർ മറ്റൊരു ഉപകരണത്തിൽ Signal-ൽ രജിസ്റ്റർ ചെയ്തതിനാലാകാം ഇത്. വീണ്ടും രജിസ്റ്റർ ചെയ്യാൻ ടാപ്പുചെയ്യുക. + ഈ കോളിന് മറുപടി നൽകാൻ, നിങ്ങളുടെ മൈക്രോഫോണ്‍ ഉപയോഗിക്കാന്‍ Molly-നെ അനുവദിക്കുക. %s നിന്നുള്ള കോളിന് മറുപടി നൽകാൻ, നിങ്ങളുടെ മൈക്രോഫോണിലേക്ക് Molly-ന് ആക്സസ് നൽകുക. കോളുകൾ വിളിക്കുന്നതിനോ സ്വീകരിക്കുന്നതിനോ Molly-ന് മൈക്രോഫോൺ, ക്യാമറ അനുമതികൾ ആവശ്യമാണ്, പക്ഷേ അവ ശാശ്വതമായി നിരസിക്കപ്പെട്ടു. അപ്ലിക്കേഷൻ ക്രമീകരണങ്ങളിൽ തുടരുക, \"അനുമതികൾ\" തിരഞ്ഞെടുത്ത് \"മൈക്രോഫോൺ\", \"ക്യാമറ\" എന്നിവ പ്രവർത്തനക്ഷമമാക്കുക. ഒരു ബന്ധിപ്പിച്ച ഉപകരണത്തിൽ ഉത്തരം നൽകി. @@ -1785,8 +1880,11 @@ ഒരു പേരോ നമ്പറോ നൽകുക സ്കാൻ ചെയ്യാൻ തൊടുക + വിജയകരമായ ചേര്‍ച്ച + സുരക്ഷാ നമ്പർ പരിശോധിക്കുന്നതിൽ പരാജയപ്പെട്ടു ലഭ്യമാക്കുന്നു പരിശോധിച്ചതായി അടയാളപ്പെടുത്തുക + പരിശോധന പൂർത്തിയാക്കുക സുരക്ഷാ നമ്പർ പങ്കിടുക @@ -1796,6 +1894,10 @@ നിരസിക്കാൻ താഴേക്ക് സ്വൈപ്പുചെയ്യുക ചില പ്രശ്‌നങ്ങൾക്ക് നിങ്ങളുടെ ശ്രദ്ധ ആവശ്യമാണ്. + അയച്ചത്: + സ്വീകരിച്ചത്: + അപ്രത്യക്ഷമാകുന്നത്: + വഴി: ശേഷിക്കുന്നു അയച്ചത് @@ -1837,6 +1939,16 @@ ഡീബഗ് ലോഗ്: ലോഗുകൾ അപ്‌ലോഡുചെയ്യാനായില്ല പ്രശ്നം മനസിലാക്കാൻ ഞങ്ങളെ സഹായിക്കുന്നതിന് ദയവായി കഴിയുന്നത്ര വിവരണാത്മകമായിരിക്കുക. + + \-\- ദയവായി ഒരു ഓപ്ഷൻ തിരഞ്ഞെടുക്കുക \-\- + എന്തോ പ്രവർത്തിക്കുന്നില്ല + ഫീച്ചർ അഭ്യർത്ഥന + ചോദ്യം + പ്രതികരണം + മറ്റുള്ളവ + പേയ്മെന്റുകൾ(മൊബൈൽകോയിൻ) + പരിപാലകരും Signal ബൂസ്റ്റും + ഈ സന്ദേശം അടുത്തിടെ ഉപയോഗിച്ചവ @@ -1933,6 +2045,8 @@ സഹായം വിപുലമായ Molly-ന് സംഭാവന ചെയ്യുക + Signal പരിപാലകര്‍ ആകുക + Signal ബൂസ്റ്റ് സ്വകാര്യത MMS യൂസർ ഏജൻറ് സ്വമേധയാലുള്ള MMS ക്രമീകരണങ്ങൾ @@ -2215,6 +2329,10 @@ ഒന്നിലധികം തിരഞ്ഞെടുക്കുക + + %d തിരഞ്ഞെടുത്തു + %d തിരഞ്ഞെടുത്തു + സംരക്ഷിക്കൂ @@ -2466,6 +2584,7 @@ പരിശോധിക്കുക നിങ്ങൾ ബാക്കപ്പിന്റെ രഹസ്യവാചകം വിജയകരമായി നൽകി രഹസ്യവാചകം ശരിയായിരുന്നില്ല + Molly ബാക്കപ്പ് സൃഷ്‌ടിക്കുന്നു… ബാക്കപ്പ് പരാജയപ്പെട്ടു  നിങ്ങളുടെ ബാക്കപ്പ് ഡയറക്ടറി ഇല്ലാതാക്കി അല്ലെങ്കിൽ നീക്കി. ഈ വോളിയത്തിൽ സംഭരിക്കാനുംമാത്രം നിങ്ങളുടെ ബാക്കപ്പ് ഫയൽ വളരെ വലുതാണ്. @@ -2588,6 +2707,7 @@ രണ്ട് ഉപകരണങ്ങളും പരസ്പരം അടുത്ത് വയ്ക്കുക. ഉപകരണങ്ങൾ ഓഫ് ചെയ്യരുത്, Molly തുറന്നിടുക. ട്രാൻസ്ഫറുകൾ എൻഡ്-ടു-എൻഡ് എൻക്രിപ്റ്റ് ചെയ്തിരിക്കുന്നു. ഇതുവരെ %1$d സന്ദേശങ്ങൾ + ഇതുവരെ %1$s %% സന്ദേശങ്ങള്‍ … റദ്ദാക്കൂ വീണ്ടും ശ്രമിക്കുക കൈമാറ്റം നിർത്തണോ? @@ -2638,6 +2758,7 @@ തടഞ്ഞത് മാറ്റുക കോൺടാക്റ്റുകളിൽ ചേർക്കൂ + കോൺ‌ടാക്റ്റുകൾ തുറക്കാൻ കഴിയുന്ന ഒരു അപ്ലിക്കേഷൻ കണ്ടെത്താനായില്ല. ഒരു ഗ്രൂപ്പിലേക്ക് ചേർക്കുക മറ്റൊരു ഗ്രൂപ്പിലേക്ക് ചേർക്കുക സുരക്ഷാ നമ്പർ കാണുക @@ -2737,7 +2858,9 @@ പ്രാദേശിക ഡാറ്റ ഇല്ലാതാക്കാൻ പരാജയപെട്ടു. സിസ്റ്റം അപ്ലിക്കേഷൻ ക്രമീകരണങ്ങളിൽ നിങ്ങൾക്ക് ഇത് സ്വമേധയാ ഇല്ലാതാക്കാം. ആപ്പ് ക്രമീകരണങ്ങൾ തുറക്കുക + ഗ്രൂപ്പുകള്‍ വിട്ടുപോകുന്നു… + അക്കൌണ്ട് ഇല്ലാതാക്കുന്നു… @@ -2902,6 +3025,7 @@ നിങ്ങളുടെ ഫോൺ നമ്പർ വീണ്ടും Signal-ൽ രജിസ്റ്റർ ചെയ്യുന്നതിന് നിങ്ങളുടെ Signal പിൻ ആവശ്യമാണ് ഫോൺ നമ്പർ മാറ്റുക + നിങ്ങളുടെ നിലവിലെ ഫോൺ നമ്പർ ഒരു പുതിയ ഫോൺ നമ്പറിലേക്ക് മാറ്റാൻ ഇത് ഉപയോഗിക്കുക. നിങ്ങൾക്ക് ഈ മാറ്റം പഴയപടിയാക്കാനാകില്ല.\n\nതുടരുന്നതിന് മുമ്പ്, നിങ്ങളുടെ പുതിയ നമ്പറിന് SMS അല്ലെങ്കിൽ കോളുകൾ ലഭിക്കുമെന്ന് ഉറപ്പാക്കുക. തുടരുക നിങ്ങളുടെ ഫോൺ നമ്പർ മാറ്റി @@ -2911,11 +3035,17 @@ നിങ്ങളുടെ പുതിയ ഫോൺ നമ്പർ പുതിയ ഫോൺ നമ്പർ നിങ്ങൾ നൽകിയ ഫോൺ നമ്പർ നിങ്ങളുടെ അക്കൗണ്ടുമായി പൊരുത്തപ്പെടുന്നില്ല. + നിങ്ങളുടെ പഴയ നമ്പറിന്റെ രാജ്യ കോഡ് വ്യക്തമാക്കണം + നിങ്ങളുടെ പഴയ ഫോൺ നമ്പർ വ്യക്തമാക്കണം + നിങ്ങളുടെ പുതിയ നമ്പറിന്റെ രാജ്യ കോഡ് വ്യക്തമാക്കണം + നിങ്ങളുടെ പുതിയ ഫോൺ നമ്പർ വ്യക്തമാക്കണം നമ്പർ മാറ്റുക %1$sഉറപ്പാക്കുന്നു + ക്യാപ്ച ആവശ്യമാണ് നമ്പർ മാറ്റുക + നിങ്ങളുടെ ഫോൺ നമ്പർ %1$s-ൽ നിന്ന് %2$s-ലേക്ക് മാറ്റാൻ പോകുകയാണ്.\n\nതുടരുന്നതിന് മുമ്പ്, ചുവടെയുള്ള നമ്പർ ശരിയാണോയെന്ന് പരിശോധിക്കുക. നമ്പർ തിരുത്തുക @@ -3103,6 +3233,7 @@ .5x 1x + 1.5x 2x പുതിയ പേയ്മെന്റ് @@ -3154,10 +3285,19 @@ സന്ദേശം അയയ്‌ക്കുന്നത് പരാജയപ്പെട്ടു. സന്ദേശങ്ങള്‍ അയയ്‌ക്കുന്നത് പരാജയപ്പെട്ടു. + + സന്ദേശം ലഭ്യമല്ലാത്തതിനാൽ ഫോർവേഡ് ചെയ്യാനായില്ല. + സന്ദേശങ്ങള്‍ ഇനി ലഭ്യമല്ലാത്തതിനാൽ ഫോർവേഡ് ചെയ്യാനായില്ല. + + പരിധി എത്തി ഒരു സന്ദേശം ചേര്‍ക്കുക ഒരു മറുപടി ചേർക്കുക റദ്ദാക്കൂ + വരയ്ക്കുക + വാചകം എഴുതുക + സ്റ്റിക്കർ ചേർക്കുക + മങ്ങിക്കുക എല്ലാം മായ്ക്കുക തിരിച്ചാക്കുക ഇല്ലാതാക്കൂ @@ -3166,9 +3306,12 @@ കളയുക പ്രിവ്യൂ നിങ്ങളെപ്പോലുള്ളവരാണ് Signal പ്രവർത്തിപ്പിക്കുന്നത്. + നാണയം + പണമടയ്ക്കാനുള്ള കൂടുതല്‍ മാർഗങ്ങൾ ഇപ്പോൾ വേണ്ട സ്ഥിരീകരിക്കുക അപ്‌ഡേറ്റ് + നിങ്ങളുടെ പിന്തുണയ്ക്ക് നന്ദി! ചെയ്‌തു ബാഡ്ജുകള്‍ ഇഷ്ടാനുസൃത തുക നൽകുക @@ -3268,6 +3411,7 @@ + ഒരു പ്രൊഫൈൽ സൃഷ്ടിക്കുക ഇപ്പോൾ വേണ്ട diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index ea2a19a4a6..5beb2fa990 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -233,8 +233,8 @@ Annuleren Gesprek verwijderen? Groep verwijderen en verlaten? - Dit gesprek zal worden gewist van al je apparaten. - Je zult deze groep verlaten en het gesprek zal van al je apparaten worden gewist. + Dit gesprek zal op al je eigen apparaten worden gewist. + Je zult deze groep verlaten en het gesprek zal op al je eigen apparaten worden gewist. Verwijderen Verwijderen en verlaten Molly heeft toegang nodig tot je microfoon om %1$s te bellen. @@ -1550,7 +1550,7 @@ Molly heeft toegang tot externe opslag nodig om iets op te slaan op de externe opslag, maar deze is permanent geweigerd. Ga naar de instellingen voor deze app, tik op ‘App-machtigingen’ en schakel ‘Opslagruimte’ in. Kan niet opslaan naar externe opslag zonder machtiging Bericht wissen? - Dit bericht zal alleen voor jou onherroepelijk gewist worden, maar dus niet voor je gesprekspartner(s). + Dit bericht zal alleen voor jou onherroepelijk worden gewist, maar dus niet voor je gesprekspartner(s). %1$s naar %2$s Media niet langer beschikbaar. Geen app gevonden waarmee dit bestand met anderen gedeeld kan worden. @@ -2513,7 +2513,7 @@ Meer lezen Voer je pincode in - Voer de pincode in welke je eerder hebt aangemaakt om informatie versleuteld op te slaan op Signals servers. Dit is niet dezelfde code als je sms-verificatiecode. + Voer de pincode in welke je eerder hebt aangemaakt om informatie versleuteld op te slaan op Signals servers. Dit is niet dezelfde code als de telefoonnummer verificatie code die je per sms of telefoonoproep hebt ontvangen. Alfanumerieke pincode invoeren Voer numerieke pincode in. Foutieve pincode, probeer het opnieuw. diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 9c6b335de6..61d70934be 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -1084,7 +1084,7 @@ Marcou como não verificado o seu número de segurança com %s Marcou como não verificado o seu número de segurança com %s a partir de outro dispositivo Não pode ser entregue uma mensagem de %s - %1$s alterou o número de telemóvel para um novo número. + %1$s alterou o número de telefone para um número novo. %1$s iniciou uma chamada em grupo · %2$s %1$s está na chamada de grupo · %2$s @@ -1505,7 +1505,7 @@ Os nomes de utilizadores não podem começar com um número. Nome de utilizador inválido. Os nomes de utilizadores devem ter entre %1$d e %2$d caracteres. - Os nomes de utilizadores no Signal são opcionais. Se escolher criar um nome de utilizador, os outros utilizadores do Signal serão capazes de encontrá-lo através desse nome de utilizador e contactá-lo sem saber o seu número de telemóvel. + Os nomes de utilizadores no Signal são opcionais. Se escolher criar um nome de utilizador, os outros utilizadores do Signal serão capazes de encontrá-lo através desse nome de utilizador e contactá-lo sem saber o seu número de telefone. %d contacto está no Signal! %d contactos estão no Signal! @@ -1888,7 +1888,7 @@ Introduza um nome ou número - Mais informações.]]> + Mais informações.]]> Toque para ler Correspondência bem sucedida Falha o verificar o número de segurança @@ -2504,7 +2504,7 @@ PIN incorreto. Tente novamente. Conta bloqueada - A sua conta foi bloqueada para proteger a sua privacidade e segurança. Após %1$d dias de inatividade ser-lhe-á permitido voltar a registar este número de telemóvel sem necessitar do seu PIN. Todo o conteúdo será eliminado. + A sua conta foi bloqueada para proteger a sua privacidade e segurança. Após %1$d dias de inatividade, ser-lhe-á permitido voltar a registar este número de telefone sem necessitar do seu PIN. Todo o conteúdo será eliminado. Seguinte Saber mais @@ -2608,13 +2608,13 @@ Código Incorrecto Nunca Desconhecido - Ver o meu número de telemóvel - Encontrar-me através do número de telemóvel + Ver o meu número de telefone + Encontrar-me através do número de telefone Todos Os meus contactos Ninguém - O seu número de telemóvel ficará visível para todas as pessoas e grupos com quem troque mensagens. - Qualquer pessoa que tenha o seu número de telemóvel nos seus contactos irá vê-lo como contacto no Signal. As outras pessoas poderão encontrá-lo através da pesquisa. + O seu número de telefone ficará visível para todas as pessoas e grupos com quem troque mensagens. + Qualquer pessoa que tenha o seu número de telefone nos seus contactos irá vê-lo como contacto no Signal. As outras pessoas poderão encontrá-lo através da pesquisa. Bloqueio de ecrã Bloqueie o acesso ao Signal utilizando o bloqueio de ecrã do Android ou a impressão digital Bloqueio de ecrã devido a inatividade @@ -2623,7 +2623,7 @@ Alterar o seu PIN Lembretes de PIN Os PINs mantém encriptada a informação guardada no Signal de forma a que apenas você lhe possa aceder. O seu perfil, definições e contactos serão restaurados quando reinstalar o Signal. - Adicione uma segurança extra ao pedir o seu PIN do Signal para registar o seu número de telemóvel novamente com o Signal. + Adicione uma segurança extra ao pedir o seu PIN do Signal para registar o seu número de telefone novamente com o Signal. Os lembretes ajudam-no a lembrar-se do seu PIN, pois ele não pode ser recuperado. Este, ser-lhe-á solicitado com menos frequência ao longo do tempo. Desativar Confirmar PIN @@ -2861,7 +2861,7 @@ Eliminar %1$s na sua conta de pagamentos Sem código de país especificado Sem número especificado - O número de telemóvel que introduziu não coincide com as suas contas. + O número de telefone que introduziu não coincide com as suas contas. Deseja realmente eliminar a sua conta? Isto irá eliminar a sua conta do Signal e repor a aplicação. A aplicação irá encerrar depois do processo estar completo. Falha ao eliminar conta. Tem alguma ligação à rede? @@ -3036,29 +3036,29 @@ Conta Ser-lhe-á pedido menos frequentemente ao longo do tempo Exija o seu PIN do Signal para registar novamente o seu número de telefone com o Signal. - Alterar o número de telemóvel + Alterar o número de telefone - Utilize isto para alterar o seu número atual para um número de telemóvel novo. Não pode desfazer esta alteração.\n\nAntes de continuar, assegure-se que o seu número novo pode receber SMS ou chamadas. + Utilize isto para alterar o seu número atual para um número de telefone novo. Não pode desfazer esta alteração.\n\nAntes de continuar, assegure-se que o seu número novo pode receber SMS ou chamadas. Continuar - O seu número de telemóvel foi alterado. + O seu número de telefone foi alterado. Alterar número O seu número antigo - Número de telemóvel antigo + Número de telefone antigo O seu número novo - Número de telemóvel novo - O número de telemóvel que introduziu não coincide com as suas contas. + Número de telefone novo + O número de telefone que introduziu não coincide com as suas contas. Deverá especificar o código do país do seu número antigo - Deverá especificar o seu número de telemóvel antigo + Deverá especificar o seu número de telefone antigo Deverá especificar o código do país do seu número novo - Deverá especificar o seu número de telemóvel novo + Deverá especificar o seu número de telefone novo Alterar número A verificar %1$s Captcha necessário Alterar número - Está prestes a alterar p seu número de telemóvel de %1$s para %2$s.\n\n\Antes de prosseguir, confirme que o número abaixo se encontra correto. + Está prestes a alterar o seu número de telefone de %1$s para %2$s.\n\n\Antes de prosseguir, confirme que o número abaixo se encontra correto. Editar número Alterar o número do Signal - Necessita de ajuda com o PIN para Android (v2 PIN) @@ -3069,7 +3069,7 @@ Atualizar PIN Manter o PIN antigo? - Parece que você tentou mudar o seu número de telemóvel. Aguardamos a confirmação de que a mudança foi bem-sucedida.\n\nA verificar agora… + Parece que você tentou mudar o seu número de telefone. Aguardamos a confirmação de que a mudança foi bem-sucedida.\n\nA verificar agora… Estado da alteração confirmado Seu número foi confirmado como %1$s. Se esse não for o seu novo número, refaça o processo de alteração de número. Estado da alteração não confirmado diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 10ab8d8a02..d65d2509a8 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -2806,7 +2806,7 @@ Am primit mesajul conform căruia schimbul de chei a avut loc pentru o versiune Nu se poate transfera dintr-o versiune mai nouă de Signal Se transmit datele - Ține ambele dispozitive unul lângă altul. Nu opri dispozitivele și menține Molly deschis. Transferurile sunt criptate integral. + Ține ambele dispozitive unul lângă celălalt. Nu opri dispozitivele și menține Molly deschis. Transferul este criptat integral. %1$d mesaje până acum… %1$s%% din mesaje până acum… @@ -2833,7 +2833,7 @@ Am primit mesajul conform căruia schimbul de chei a avut loc pentru o versiune Datele tale Signal au fost transferate pe noul dispozitiv. Pentru a finaliza procesul de transfer, trebuie să continui înregistrarea pe noul dispozitiv. Închide - Transferul a fost făcut cu succes + Transferul a reușit Transfer finalizat Pentru a finaliza procesul de transfer, trebuie să continui înregistrarea. Continuă înregistrarea @@ -3153,7 +3153,7 @@ Am primit mesajul conform căruia schimbul de chei a avut loc pentru o versiune Captcha este necesară Schimbă Numărul - Urmează să îți schimbi numărul de telefon din %1$s în %2$s.\n\nÎnainte de a continua, te rugăm să te asiguri că numărul de mai jos este corect. + Urmează să îți schimbi numărul de telefon din %1$s în %2$s.\n\nÎnainte de a continua, te rugăm să verifici dacă numărul de mai jos este corect. Editează număr Schimbare Număr Signal - Am nevoie de asistență pentru PIN Android (v2 PIN) diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 456fbd3587..6e0147f544 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -2427,6 +2427,11 @@ Изабери више + + Одабрано %d + Одабрано %d + Одабрано %d + Сачувај diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 19ed925b39..aec0aedca3 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -1293,6 +1293,7 @@ Högtalare Kamera Tysta + Ring Avsluta samtal diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 066203f7da..d6cfb967e2 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -412,6 +412,7 @@ Devam ediyor… Şu ana kadar %1$d… + Şu ana kadar %1$s%% … Molly, yedek oluşturabilmek için Depolama iznine ihtiyaç duyar, fakat bu izin kalıcı olarak reddedilmiş. Lütfen uygulama ayarları menüsüne girip \"İzinler\" kısmını seçin, ve \"Depolama\"yı etkinleştirin. Özelleştirilmiş \'%s\' kullanılıyor @@ -2338,6 +2339,10 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. Çoklu seçim + + %d seçili + %d seçili + Kaydet @@ -2711,6 +2716,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. Her iki aygıtı birbirine yakın tutun. Aygıtları kapatmayın ve Molly\'i açık tutun. Aktarımlar uçtan uça şifrelidir. Şimdiye kadar %1$d ileti… + Şu ana kadar %1$s%% ileti… İptal Tekrar dene Aktarımı durdur? @@ -3427,7 +3433,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. Ödemenin işlenmesinde hata Ödemenin işlenmesinde hata. %1$s - Rozetin hesabına eklenemedi ama ödediğin ücret alınmış olabilir. Lütfen destek ekibiyle iletişime geç. + Rozetiniz hesabınıza eklenemedi fakat ücret ödenmiş olabilir. Lütfen destek ekibiyle iletişime geçin. Ödemeniz işlenemedi ve sizden ödeme alınmadı. Lütfen tekrar deneyin. İşlem sürüyor Rozet eklenemedi @@ -3438,10 +3444,10 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. Abonelik iptal edilemedi Abonelik iptali için internet bağlantısı gerekir. Cihazın Google Pay\'i desteklemiyor, bu yüzden abone olup rozet kazanamazsın. Yine de websitemizden bağışta bulunarak Signal\'ı destekleyebilirsin. - Ağ hatası. Bağlantını kontrol edip tekrar dene. + Ağ hatası. Bağlantınızı kontrol edip tekrar deneyin. Tekrar dene - Profilinizi isimlendirin + Profilinizi adlandırın Profil adı @@ -3455,16 +3461,17 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. Profili düzenleyin - Bu isimle bir profil mevcut + Bu ada sahip bir profil zaten mevcut İş - uyku + Uyku + Araç sürme - Dinlenme süresi + Dinlenme - Odaklan + Odaklanma Bir ad olmalı @@ -3486,7 +3493,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. Profili sil - \"%1$s\" kaldırıldı. + \"%1$s\" çıkarıldı. Geri al @@ -3510,19 +3517,19 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. Tüm bahsetmeleri bildir - Zamanlama + Zamanlayıcı Tümünü gör - Zamanlama ekle + Zamanlayıcı ekle - Bu bildirim profilini otomatik olarak aktifleştirmek için bir program kurun. + Bu bildirim profilini otomatik olarak etkinleştirmek için zamanlayıcı ayarlayın - Zamanlama + Zamanlayıcı - Başlat + Başlangıç - Son + Bitiş P @@ -3564,7 +3571,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı. 1 saatliğine - %1$s tarihine kadar + Saat %1$s olana kadar Ayarları görüntüle diff --git a/app/src/test/java/org/thoughtcrime/securesms/emoji/EmojiJsonParserTest.kt b/app/src/test/java/org/thoughtcrime/securesms/emoji/EmojiJsonParserTest.kt index 84bf452f1b..bc2816fb42 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/emoji/EmojiJsonParserTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/emoji/EmojiJsonParserTest.kt @@ -51,6 +51,30 @@ private const val SAMPLE_JSON_WITH_OBSOLETE = """ } """ +private const val SAMPLE_JSON_WITH_JUMBOS = """ + { + "emoji": { + "Places_1": [["0002"], ["0003", "0004", "0005"]], + "Places_2": [["0003"], ["0008", "0009", "0000"]], + "Foods": [["0001"], ["0002", "0003", "0004"]] + }, + "jumbomoji": { + "People_0": ["d83dde00","d83dde03","d83dde04", "d83dde01"], + "People_1": ["ad83dde00","ad83dde03","ad83dde04", "ad83dde01"] + }, + "obsolete": [ + {"obsoleted": "0012", "replace_with": "0023"} + ], + "metrics": { + "raw_height": 64, + "raw_width": 64, + "per_row": 16 + }, + "densities": [ "xhdpi" ], + "format": "png" + } +""" + private val SAMPLE_JSON_WITHOUT_OBSOLETE_EXPECTED = listOf( StaticEmojiPageModel(EmojiCategory.FOODS, listOf(Emoji("\u0001"), Emoji("\u0002", "\u0003", "\u0004")), Uri.parse("file:///Foods")), StaticEmojiPageModel(EmojiCategory.PLACES, listOf(Emoji("\ud83c\udf0d"), Emoji("\u0003", "\u0004", "\u0005")), Uri.parse("file:///Places")) @@ -128,6 +152,23 @@ class EmojiJsonParserTest { Assert.assertEquals(result.format, "png") } + @Test + fun `Given sample with jumbo, when I parse, then I expect source with jumbo map`() { + val result: ParsedEmojiData = EmojiJsonParser.parse(SAMPLE_JSON_WITH_JUMBOS.byteInputStream(), this::uriFactory).getOrThrow() + + val jumboPages = result.jumboPages + + Assert.assertEquals("People_0", jumboPages["d83dde00"]) + Assert.assertEquals("People_0", jumboPages["d83dde03"]) + Assert.assertEquals("People_0", jumboPages["d83dde04"]) + Assert.assertEquals("People_0", jumboPages["d83dde01"]) + + Assert.assertEquals("People_1", jumboPages["ad83dde00"]) + Assert.assertEquals("People_1", jumboPages["ad83dde03"]) + Assert.assertEquals("People_1", jumboPages["ad83dde04"]) + Assert.assertEquals("People_1", jumboPages["ad83dde01"]) + } + private fun uriFactory(sprite: String, format: String) = Uri.parse("file:///$sprite") private fun EmojiPageModel.isSameAs(other: EmojiPageModel) = diff --git a/app/src/test/java/org/thoughtcrime/securesms/emoji/EmojiSourceTest.kt b/app/src/test/java/org/thoughtcrime/securesms/emoji/EmojiSourceTest.kt index 33d270b374..3e213b7afe 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/emoji/EmojiSourceTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/emoji/EmojiSourceTest.kt @@ -10,7 +10,7 @@ class EmojiSourceTest { @Test fun `Given a bunch of data pages with max value 100100, when I get the maxEmojiLength, then I expect 6`() { - val emojiDataFake = ParsedEmojiData(EmojiMetrics(-1, -1, -1), listOf(), "png", listOf(), dataPages = generatePages(), listOf()) + val emojiDataFake = ParsedEmojiData(EmojiMetrics(-1, -1, -1), listOf(), "png", listOf(), dataPages = generatePages(), emptyMap(), listOf()) val testSubject = EmojiSource(0f, emojiDataFake) { uri -> EmojiPage.Disk(uri) } Assert.assertEquals(6, testSubject.maxEmojiLength) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java index a29fee16eb..782f294e2a 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java @@ -1623,7 +1623,7 @@ private SendMessageResult sendMessage(SignalServiceAddress recipient, if (!unidentifiedAccess.isPresent()) { try { SendMessageResponse response = new MessagingService.SendResponseProcessor<>(messagingService.send(messages, Optional.absent()).blockingGet()).getResultOrThrow(); - return SendMessageResult.success(recipient, messages.getDevices(), false, response.getNeedsSync() || store.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent()); + return SendMessageResult.success(recipient, messages.getDevices(), response.sentUnidentified(), response.getNeedsSync() || store.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent()); } catch (WebSocketUnavailableException e) { Log.i(TAG, "[sendMessage][" + timestamp + "] Pipe unavailable, falling back... (" + e.getClass().getSimpleName() + ": " + e.getMessage() + ")"); } catch (IOException e) { @@ -1633,7 +1633,7 @@ private SendMessageResult sendMessage(SignalServiceAddress recipient, } else if (unidentifiedAccess.isPresent()) { try { SendMessageResponse response = new MessagingService.SendResponseProcessor<>(messagingService.send(messages, unidentifiedAccess).blockingGet()).getResultOrThrow(); - return SendMessageResult.success(recipient, messages.getDevices(), true, response.getNeedsSync() || store.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent()); + return SendMessageResult.success(recipient, messages.getDevices(), response.sentUnidentified(), response.getNeedsSync() || store.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent()); } catch (WebSocketUnavailableException e) { Log.i(TAG, "[sendMessage][" + timestamp + "] Unidentified pipe unavailable, falling back... (" + e.getClass().getSimpleName() + ": " + e.getMessage() + ")"); } catch (IOException e) { @@ -1648,7 +1648,7 @@ private SendMessageResult sendMessage(SignalServiceAddress recipient, SendMessageResponse response = socket.sendMessage(messages, unidentifiedAccess); - return SendMessageResult.success(recipient, messages.getDevices(), unidentifiedAccess.isPresent(), response.getNeedsSync() || store.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent()); + return SendMessageResult.success(recipient, messages.getDevices(), response.sentUnidentified(), response.getNeedsSync() || store.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent()); } catch (InvalidKeyException ike) { Log.w(TAG, ike); @@ -1745,12 +1745,12 @@ private List sendGroupMessage(DistributionId dist .filter(r -> !r.isSuccess()) .collect(Collectors.toList()); - Set failedAddresses = trueFailures.stream() - .map(SendMessageResult::getAddress) - .collect(Collectors.toSet()); + Set failedAddresses = trueFailures.stream() + .map(result -> result.getAddress().getAci()) + .collect(Collectors.toSet()); List fakeNetworkFailures = recipients.stream() - .filter(r -> !failedAddresses.contains(r)) + .filter(r -> !failedAddresses.contains(r.getAci())) .map(SendMessageResult::networkFailure) .collect(Collectors.toList()); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java index f98dcd880f..11e7e63e62 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java @@ -36,6 +36,7 @@ import org.whispersystems.libsignal.SignalProtocolAddress; import org.whispersystems.libsignal.UntrustedIdentityException; import org.whispersystems.libsignal.groups.GroupCipher; +import org.whispersystems.libsignal.logging.Log; import org.whispersystems.libsignal.protocol.CiphertextMessage; import org.whispersystems.libsignal.protocol.PreKeySignalMessage; import org.whispersystems.libsignal.protocol.SignalMessage; @@ -202,9 +203,15 @@ private Plaintext decrypt(SignalServiceEnvelope envelope, byte[] ciphertext) DecryptionResult result = sealedSessionCipher.decrypt(certificateValidator, ciphertext, envelope.getServerReceivedTimestamp()); SignalServiceAddress resultAddress = new SignalServiceAddress(ACI.parseOrThrow(result.getSenderUuid()), result.getSenderE164()); Optional groupId = result.getGroupId(); + boolean needsReceipt = true; + + if (envelope.hasSourceUuid()) { + Log.w(TAG, "[" + envelope.getTimestamp() + "] Received a UD-encrypted message sent over an identified channel. Marking as needsReceipt=false"); + needsReceipt = false; + } paddedMessage = result.getPaddedMessage(); - metadata = new SignalServiceMetadata(resultAddress, result.getDeviceId(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), true, envelope.getServerGuid(), groupId); + metadata = new SignalServiceMetadata(resultAddress, result.getDeviceId(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), needsReceipt, envelope.getServerGuid(), groupId); } else { throw new InvalidMetadataMessageException("Unknown type: " + envelope.getType()); } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/MessagingService.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/MessagingService.java index cc11b22f6b..0655ba734f 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/MessagingService.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/MessagingService.java @@ -2,6 +2,7 @@ import com.google.protobuf.ByteString; +import org.whispersystems.libsignal.logging.Log; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalWebSocket; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; @@ -52,9 +53,11 @@ public Single> send(OutgoingPushMessageList .build(); ResponseMapper responseMapper = DefaultResponseMapper.extend(SendMessageResponse.class) - .withResponseMapper((status, body, getHeader) -> { - SendMessageResponse sendMessageResponse = Util.isEmpty(body) ? new SendMessageResponse(false) + .withResponseMapper((status, body, getHeader, unidentified) -> { + SendMessageResponse sendMessageResponse = Util.isEmpty(body) ? new SendMessageResponse(false, unidentified) : JsonUtil.fromJsonResponse(body, SendMessageResponse.class); + sendMessageResponse.setSentUnidentfied(unidentified); + return ServiceResponse.forResult(sendMessageResponse, status, body); }) .withCustomError(404, (status, body, getHeader) -> new UnregisteredUserException(list.getDestination(), new NotFoundException("not found"))) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java index b8384ba74a..4586c953cb 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java @@ -125,7 +125,7 @@ public ProfileResponseMapper(SignalServiceProfile.RequestType requestType, Profi } @Override - public ServiceResponse map(int status, String body, Function getHeader) + public ServiceResponse map(int status, String body, Function getHeader, boolean unidentified) throws MalformedResponseException { try { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/ServiceResponse.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/ServiceResponse.java index d82af3ccd8..c73763c9f9 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/ServiceResponse.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/ServiceResponse.java @@ -11,7 +11,7 @@ import io.reactivex.rxjava3.core.Single; /** - * Encapsulates a parsed APi response regardless of where it came from (WebSocket or REST). Not only + * Encapsulates a parsed API response regardless of where it came from (WebSocket or REST). Not only * includes the success result but also any application errors encountered (404s, parsing, etc.) or * execution errors encountered (IOException, etc.). */ 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 1930704360..495a304018 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 @@ -505,7 +505,7 @@ public SendMessageResponse sendMessage(OutgoingPushMessageList bundle, Optional< try { String responseText = makeServiceRequest(String.format(MESSAGE_PATH, bundle.getDestination()), "PUT", JsonUtil.toJson(bundle), NO_HEADERS, unidentifiedAccess); - if (responseText == null) return new SendMessageResponse(false); + if (responseText == null) return new SendMessageResponse(false, unidentifiedAccess.isPresent()); else return JsonUtil.fromJson(responseText, SendMessageResponse.class); } catch (NotFoundException nfe) { throw new UnregisteredUserException(bundle.getDestination(), nfe); @@ -516,7 +516,7 @@ public Future submitMessage(OutgoingPushMessageList bundle, ListenableFuture response = submitServiceRequest(String.format(MESSAGE_PATH, bundle.getDestination()), "PUT", JsonUtil.toJson(bundle), NO_HEADERS, unidentifiedAccess); return FutureTransformers.map(response, body -> { - return body == null ? new SendMessageResponse(false) + return body == null ? new SendMessageResponse(false, unidentifiedAccess.isPresent()) : JsonUtil.fromJson(body, SendMessageResponse.class); }); } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SendMessageResponse.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SendMessageResponse.java index ac129f2716..36457ceb48 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SendMessageResponse.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SendMessageResponse.java @@ -1,16 +1,30 @@ package org.whispersystems.signalservice.internal.push; +import com.fasterxml.jackson.annotation.JsonProperty; + public class SendMessageResponse { + @JsonProperty private boolean needsSync; + private boolean sentUnidentfied; + public SendMessageResponse() {} - public SendMessageResponse(boolean needsSync) { - this.needsSync = needsSync; + public SendMessageResponse(boolean needsSync, boolean sentUnidentified) { + this.needsSync = needsSync; + this.sentUnidentfied = sentUnidentified; } public boolean getNeedsSync() { return needsSync; } + + public boolean sentUnidentified() { + return sentUnidentfied; + } + + public void setSentUnidentfied(boolean value) { + this.sentUnidentfied = value; + } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/DefaultResponseMapper.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/DefaultResponseMapper.java index e622be739f..3b39dd20e1 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/DefaultResponseMapper.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/DefaultResponseMapper.java @@ -41,12 +41,12 @@ private DefaultResponseMapper(Class clazz, CustomResponseMapper map(int status, String body, Function getHeader) { + public ServiceResponse map(int status, String body, Function getHeader, boolean unidentified) { Throwable applicationError = errorMapper.parseError(status, body, getHeader); if (applicationError == null) { try { if (customResponseMapper != null) { - return Objects.requireNonNull(customResponseMapper.map(status, body, getHeader)); + return Objects.requireNonNull(customResponseMapper.map(status, body, getHeader, unidentified)); } return ServiceResponse.forResult(JsonUtil.fromJsonResponse(body, clazz), status, body); } catch (MalformedResponseException e) { @@ -81,6 +81,6 @@ public ResponseMapper build() { } public interface CustomResponseMapper { - ServiceResponse map(int status, String body, Function getHeader) throws MalformedResponseException; + ServiceResponse map(int status, String body, Function getHeader, boolean unidentified) throws MalformedResponseException; } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/ResponseMapper.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/ResponseMapper.java index 0b202ae65b..1ef4c23075 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/ResponseMapper.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/ResponseMapper.java @@ -15,9 +15,9 @@ * @param - The final type the API response will map into. */ public interface ResponseMapper { - ServiceResponse map(int status, String body, Function getHeader); + ServiceResponse map(int status, String body, Function getHeader, boolean unidentified); default ServiceResponse map(WebsocketResponse response) { - return map(response.getStatus(), response.getBody(), response::getHeader); + return map(response.getStatus(), response.getBody(), response::getHeader, response.isUnidentified()); } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebSocketConnection.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebSocketConnection.java index 9185991d12..dc7a883c7a 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebSocketConnection.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebSocketConnection.java @@ -262,7 +262,8 @@ public synchronized void onMessage(WebSocket webSocket, ByteString payload) { if (listener != null) { listener.onSuccess(new WebsocketResponse(message.getResponse().getStatus(), new String(message.getResponse().getBody().toByteArray()), - message.getResponse().getHeadersList())); + message.getResponse().getHeadersList(), + !credentialsProvider.isPresent())); if (message.getResponse().getStatus() >= 400) { healthMonitor.onMessageError(message.getResponse().getStatus(), credentialsProvider.isPresent()); } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebsocketResponse.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebsocketResponse.java index 3ee0282f64..9169ff613b 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebsocketResponse.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebsocketResponse.java @@ -10,11 +10,13 @@ public class WebsocketResponse { private final int status; private final String body; private final Map headers; + private final boolean unidentified; - WebsocketResponse(int status, String body, List headers) { - this.status = status; - this.body = body; - this.headers = parseHeaders(headers); + WebsocketResponse(int status, String body, List headers, boolean unidentified) { + this.status = status; + this.body = body; + this.headers = parseHeaders(headers); + this.unidentified = unidentified; } public int getStatus() { @@ -29,6 +31,10 @@ public String getHeader(String key) { return headers.get(Preconditions.checkNotNull(key.toLowerCase())); } + public boolean isUnidentified() { + return unidentified; + } + private static Map parseHeaders(List rawHeaders) { Map headers = new HashMap<>(rawHeaders.size());