diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/EmojiSearchTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/EmojiSearchTable.kt index 33b5802939..144d2ac064 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/EmojiSearchTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/EmojiSearchTable.kt @@ -93,21 +93,26 @@ class EmojiSearchTable(context: Context, databaseHelper: SignalDatabase) : Datab /** * Deletes the content of the current search index and replaces it with the new one. */ - fun setSearchIndex(searchIndex: List) { - val db = databaseHelper.signalReadableDatabase - - db.withinTransaction { + fun setSearchIndex( + localizedSearchIndex: List, + englishSearchIndex: List + ) { + databaseHelper.signalReadableDatabase.withinTransaction { db -> db.delete(TABLE_NAME, null, null) + db.insert(localizedSearchIndex) + db.insert(englishSearchIndex) + } + } - for (searchData in searchIndex) { - for (label in searchData.tags) { - val values = contentValuesOf( - LABEL to label, - EMOJI to searchData.emoji, - RANK to if (searchData.rank == 0) Int.MAX_VALUE else searchData.rank - ) - db.insert(TABLE_NAME, null, values) - } + private fun SQLiteDatabase.insert(searchIndex: List) { + for (searchData in searchIndex) { + for (label in searchData.tags) { + val values = contentValuesOf( + LABEL to label, + EMOJI to searchData.emoji, + RANK to if (searchData.rank == 0) Int.MAX_VALUE else searchData.rank + ) + insert(TABLE_NAME, null, values) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/EmojiSearchIndexDownloadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/EmojiSearchIndexDownloadJob.java index 1533fa582d..1bc0f61ff6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/EmojiSearchIndexDownloadJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/EmojiSearchIndexDownloadJob.java @@ -32,19 +32,20 @@ public final class EmojiSearchIndexDownloadJob extends BaseJob { private static final String TAG = Log.tag(EmojiSearchIndexDownloadJob.class); - public static final String KEY = "EmojiSearchIndexDownloadJob"; + public static final String KEY = "EmojiSearchIndexDownloadJob"; + public static final String LANGUAGE_CODE_ENGLISH = "en"; private static final long INTERVAL_WITHOUT_INDEX = TimeUnit.DAYS.toMillis(1); private static final long INTERVAL_WITH_INDEX = TimeUnit.DAYS.toMillis(7); private EmojiSearchIndexDownloadJob() { this(new Parameters.Builder() - .setQueue("EmojiSearchIndexDownloadJob") - .setMaxInstancesForFactory(2) - .addConstraint(NetworkConstraint.KEY) - .setLifespan(TimeUnit.DAYS.toMillis(1)) - .setMaxAttempts(Parameters.UNLIMITED) - .build()); + .setQueue("EmojiSearchIndexDownloadJob") + .setMaxInstancesForFactory(2) + .addConstraint(NetworkConstraint.KEY) + .setLifespan(TimeUnit.DAYS.toMillis(1)) + .setMaxAttempts(Parameters.UNLIMITED) + .build()); } private EmojiSearchIndexDownloadJob(@NonNull Parameters parameters) { @@ -99,14 +100,20 @@ public final class EmojiSearchIndexDownloadJob extends BaseJob { } Log.i(TAG, "Need to get a new search index. Downloading version: " + manifest.getVersion() + ", language: " + remoteLanguage); + List localizedSearchIndex = downloadSearchIndex(manifest.getVersion(), remoteLanguage); - List searchIndex = downloadSearchIndex(manifest.getVersion(), remoteLanguage); + List englishSearchIndex; + if (remoteLanguage.equals(LANGUAGE_CODE_ENGLISH) || remoteLanguage.startsWith(LANGUAGE_CODE_ENGLISH + "_")) { + englishSearchIndex = Collections.emptyList(); + } else { + englishSearchIndex = downloadSearchIndex(manifest.getVersion(), LANGUAGE_CODE_ENGLISH); + } - if (searchIndex.isEmpty()) { + if (localizedSearchIndex.isEmpty()) { throw new IOException("Emoji search data is empty"); } - SignalDatabase.emojiSearch().setSearchIndex(searchIndex); + SignalDatabase.emojiSearch().setSearchIndex(localizedSearchIndex, englishSearchIndex); SignalStore.emoji().onSearchIndexUpdated(manifest.getVersion(), remoteLanguage); SignalStore.emoji().setLastSearchIndexCheck(System.currentTimeMillis()); @@ -153,9 +160,9 @@ public final class EmojiSearchIndexDownloadJob extends BaseJob { if (parentLanguage != null) { Log.i(TAG, "No exact match found. Using parent language: " + parentLanguage); return parentLanguage; - } else if (languages.contains("en")) { - Log.w(TAG, "No match, so falling back to en locale."); - return "en"; + } else if (languages.contains(LANGUAGE_CODE_ENGLISH)) { + Log.w(TAG, "No match, so falling back to " + LANGUAGE_CODE_ENGLISH + " locale."); + return LANGUAGE_CODE_ENGLISH; } else if (languages.contains("en_US")) { Log.w(TAG, "No match, so falling back to en_US locale."); return "en_US"; 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 7ff3713329..333e1d31e4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -70,6 +70,7 @@ import org.thoughtcrime.securesms.migrations.DirectoryRefreshMigrationJob; import org.thoughtcrime.securesms.migrations.DuplicateE164MigrationJob; import org.thoughtcrime.securesms.migrations.E164FormattingMigrationJob; import org.thoughtcrime.securesms.migrations.EmojiDownloadMigrationJob; +import org.thoughtcrime.securesms.migrations.EmojiSearchEnglishLabelsMigrationJob; import org.thoughtcrime.securesms.migrations.EmojiSearchIndexCheckMigrationJob; import org.thoughtcrime.securesms.migrations.FixChangeNumberErrorMigrationJob; import org.thoughtcrime.securesms.migrations.GooglePlayBillingPurchaseTokenMigrationJob; @@ -316,6 +317,7 @@ public final class JobManagerFactories { put(DuplicateE164MigrationJob.KEY, new DuplicateE164MigrationJob.Factory()); put(E164FormattingMigrationJob.KEY, new E164FormattingMigrationJob.Factory()); put(EmojiDownloadMigrationJob.KEY, new EmojiDownloadMigrationJob.Factory()); + put(EmojiSearchEnglishLabelsMigrationJob.KEY, new EmojiSearchEnglishLabelsMigrationJob.Factory()); put(EmojiSearchIndexCheckMigrationJob.KEY, new EmojiSearchIndexCheckMigrationJob.Factory()); put(FixChangeNumberErrorMigrationJob.KEY, new FixChangeNumberErrorMigrationJob.Factory()); put(GooglePlayBillingPurchaseTokenMigrationJob.KEY, new GooglePlayBillingPurchaseTokenMigrationJob.Factory()); 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 c7bf554fa6..2bbea53cba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java @@ -188,11 +188,12 @@ public class ApplicationMigrations { static final int RESET_ARCHIVE_TIER = 144; static final int ARCHIVE_BACKUP_ID = 145; static final int QUOTE_THUMBNAIL_BACKFILL = 146; + static final int EMOJI_ENGLISH_SEARCH = 147; } - public static final int CURRENT_VERSION = 146; + public static final int CURRENT_VERSION = 147; - /** + /** * This *must* be called after the {@link JobManager} has been instantiated, but *before* the call * to {@link JobManager#beginJobLoop()}. Otherwise, other non-migration jobs may have started * executing before we add the migration jobs. @@ -505,7 +506,7 @@ public class ApplicationMigrations { } if (lastSeenVersion < Version.CHANGE_NUMBER_CAPABILITY_4) { - jobs.put(Version.CHANGE_NUMBER_CAPABILITY_4,new AttributesMigrationJob()); + jobs.put(Version.CHANGE_NUMBER_CAPABILITY_4, new AttributesMigrationJob()); } // if (lastSeenVersion < Version.KBS_MIGRATION) { @@ -686,11 +687,11 @@ public class ApplicationMigrations { } if (lastSeenVersion < Version.SELF_REGISTERTED_STATE) { - jobs.put(Version.SELF_REGISTERTED_STATE, new SelfRegisteredStateMigrationJob()); + jobs.put(Version.SELF_REGISTERTED_STATE, new SelfRegisteredStateMigrationJob()); } if (lastSeenVersion < Version.SVR2_ENCLAVE_UPDATE) { - jobs.put(Version.SVR2_ENCLAVE_UPDATE, new Svr2MirrorMigrationJob()); + jobs.put(Version.SVR2_ENCLAVE_UPDATE, new Svr2MirrorMigrationJob()); } if (lastSeenVersion < Version.STORAGE_LOCAL_UNKNOWNS_FIX) { @@ -754,7 +755,7 @@ public class ApplicationMigrations { } if (lastSeenVersion < Version.SVR2_ENCLAVE_UPDATE_2) { - jobs.put(Version.SVR2_ENCLAVE_UPDATE_2, new Svr2MirrorMigrationJob()); + jobs.put(Version.SVR2_ENCLAVE_UPDATE_2, new Svr2MirrorMigrationJob()); } if (lastSeenVersion < Version.WALLPAPER_MIGRATION_CLEANUP) { @@ -816,7 +817,7 @@ public class ApplicationMigrations { if (lastSeenVersion < Version.AVATAR_COLOR_MIGRATION_JOB) { jobs.put(Version.AVATAR_COLOR_MIGRATION_JOB, new AvatarColorStorageServiceMigrationJob()); } - + if (lastSeenVersion < Version.DUPLICATE_E164_FIX_2) { jobs.put(Version.DUPLICATE_E164_FIX_2, new DuplicateE164MigrationJob()); } @@ -869,6 +870,10 @@ public class ApplicationMigrations { jobs.put(Version.QUOTE_THUMBNAIL_BACKFILL, new QuoteThumbnailBackfillMigrationJob()); } + if (lastSeenVersion < Version.EMOJI_ENGLISH_SEARCH) { + jobs.put(Version.EMOJI_ENGLISH_SEARCH, new EmojiSearchEnglishLabelsMigrationJob()); + } + return jobs; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/EmojiSearchEnglishLabelsMigrationJob.kt b/app/src/main/java/org/thoughtcrime/securesms/migrations/EmojiSearchEnglishLabelsMigrationJob.kt new file mode 100644 index 0000000000..8625b277e9 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/EmojiSearchEnglishLabelsMigrationJob.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2025 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.migrations + +import org.thoughtcrime.securesms.jobmanager.Job +import org.thoughtcrime.securesms.jobs.EmojiSearchIndexDownloadJob +import org.thoughtcrime.securesms.keyvalue.SignalStore + +/** + * Schedules job to download both the localized and English emoji search indices, ensuring that emoji search data is available in the user's preferred + * language as well as English. + */ +internal class EmojiSearchEnglishLabelsMigrationJob(parameters: Parameters = Parameters.Builder().build()) : MigrationJob(parameters) { + companion object { + const val KEY = "EmojiSearchEnglishLabelsMigrationJob" + } + + override fun getFactoryKey(): String = KEY + override fun isUiBlocking(): Boolean = false + + override fun performMigration() { + if (EmojiSearchIndexDownloadJob.LANGUAGE_CODE_ENGLISH != SignalStore.emoji.searchLanguage) { + SignalStore.emoji.clearSearchIndexMetadata() + EmojiSearchIndexDownloadJob.scheduleImmediately() + } + } + + override fun shouldRetry(e: Exception): Boolean = false + + class Factory : Job.Factory { + override fun create(parameters: Parameters, serializedData: ByteArray?): EmojiSearchEnglishLabelsMigrationJob { + return EmojiSearchEnglishLabelsMigrationJob(parameters) + } + } +}