Update jumbomoji processing and downloading.

This commit is contained in:
Cody Henthorne
2022-01-21 10:31:43 -05:00
committed by GitHub
parent 2b021f5237
commit bfdedd57d1
21 changed files with 351 additions and 54 deletions

View File

@@ -35,6 +35,7 @@ import org.signal.core.util.logging.AndroidLogger;
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;
@@ -192,6 +193,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
.addPostRender(() -> DownloadLatestEmojiDataJob.scheduleIfNecessary(this))
.addPostRender(EmojiSearchIndexDownloadJob::scheduleIfNecessary)
.addPostRender(() -> SignalDatabase.messageLog().trimOldMessages(System.currentTimeMillis(), FeatureFlags.retryRespondMaxAge()))
.addPostRender(() -> JumboEmoji.updateCurrentVersion(this))
.execute();
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");

View File

@@ -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<String> variations;
private final List<String> rawVariations;
public Emoji(String... variations) {
this.variations = Arrays.asList(variations);
this(Arrays.asList(variations), Collections.emptyList());
}
public Emoji(List<String> variations) {
this(variations, Collections.emptyList());
}
public Emoji(List<String> variations, List<String> rawVariations) {
this.variations = variations;
this.rawVariations = rawVariations;
}
public String getValue() {
@@ -26,4 +35,11 @@ public class Emoji {
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;
}
}

View File

@@ -149,8 +149,8 @@ public class EmojiProvider {
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 class EmojiProvider {
@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 class EmojiProvider {
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);
}
}
}

View File

@@ -30,6 +30,7 @@ import org.thoughtcrime.securesms.R;
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 @@ public class EmojiTextView extends AppCompatTextView {
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);

View File

@@ -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?)

View File

@@ -24,6 +24,8 @@ package org.thoughtcrime.securesms.components.emoji.parsing;
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 class EmojiParser {
return list.size();
}
public boolean hasJumboForAll() {
for (Candidate candidate : list) {
if (!JumboEmoji.hasJumboEmoji(candidate.drawInfo)) {
return false;
}
}
return true;
}
@Override
public @NonNull Iterator<Candidate> iterator() {
return list.iterator();

View File

@@ -612,7 +612,7 @@ public abstract class MessageRecord extends DisplayRecord {
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;
}

View File

@@ -4,10 +4,12 @@ import android.content.Context;
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<InputStream> 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<Response> responseProducer,
@@ -90,6 +102,23 @@ public class EmojiDownloader {
}
}
private static void streamFromRemote(@NonNull Producer<Response> responseProducer,
@NonNull Consumer<InputStream> 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");

View File

@@ -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<ObsoleteEmoji> = node["obsolete"].toObseleteList()
val dataPages: List<EmojiPageModel> = getDataPages(format, node["emoji"], uriFactory)
val jumboPages: Map<String, String> = getJumboPages(node["jumbomoji"])
val displayPages: List<EmojiPageModel> = mergeToDisplayPages(dataPages)
val metrics: EmojiMetrics = node["metrics"].toEmojiMetrics()
val densities: List<String> = 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<EmojiPageModel> {
@@ -64,13 +66,33 @@ object EmojiJsonParser {
.toList()
}
private fun getJumboPages(jumbo: JsonNode?): Map<String, String> {
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<String> = mutableListOf()
val rawVariations: MutableList<String> = 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<EmojiPageModel>,
override val dataPages: List<EmojiPageModel>,
override val jumboPages: Map<String, String>,
override val obsolete: List<ObsoleteEmoji>
) : EmojiData

View File

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

View File

@@ -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<EmojiPageModel>
val dataPages: List<EmojiPageModel>
val jumboPages: Map<String, String>
val obsolete: List<ObsoleteEmoji>
}

View File

@@ -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<String, Bitmap> = SoftHashMap(16)
private val tasks: MutableMap<String, ListenableFutureTask<Bitmap>> = hashMapOf()
private val versionToFormat: MutableMap<UUID, String?> = hashMapOf()
private val downloadedJumbos: MutableSet<String> = 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<Bitmap>? = 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<Bitmap> {
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
}
}

View File

@@ -54,11 +54,15 @@ public class AutoDownloadEmojiConstraint implements Constraint {
}
public static boolean canAutoDownloadEmoji(@NonNull Context context) {
return getAllowedAutoDownloadTypes(context).contains(IMAGE_TYPE);
return getAllowedAutoDownloadTypes(context, true).contains(IMAGE_TYPE);
}
private static @NonNull Set<String> 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<String> 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();

View File

@@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.emoji.EmojiJsonRequest;
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 @@ public class DownloadLatestEmojiDataJob extends BaseJob {
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 @@ public class DownloadLatestEmojiDataJob extends BaseJob {
.forEach(FileUtils::deleteDirectory);
EmojiPageCache.INSTANCE.clear();
if (version != null) {
SignalStore.emojiValues().clearJumboEmojiSheets(version.getVersion());
}
}
public static final class Factory implements Job.Factory<DownloadLatestEmojiDataJob> {

View File

@@ -41,6 +41,7 @@ import org.thoughtcrime.securesms.migrations.CachedAttachmentsMigrationJob;
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.LegacyMigrationJob;
import org.thoughtcrime.securesms.migrations.MigrationCompleteJob;
@@ -187,6 +188,7 @@ public final class JobManagerFactories {
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(LegacyMigrationJob.KEY, new LegacyMigrationJob.Factory());
put(MigrationCompleteJob.KEY, new MigrationCompleteJob.Factory());

View File

@@ -11,7 +11,9 @@ import org.thoughtcrime.securesms.util.Util;
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 class EmojiValues extends SignalStoreValues {
public void setLastSearchIndexCheck(long time) {
putLong(LAST_SEARCH_CHECK, time);
}
public void addJumboEmojiSheet(int version, String sheet) {
Set<String> sheets = getJumboEmojiSheets(version);
sheets.add(sheet);
getStore().beginWrite()
.putString(JUMBO_EMOJI_DOWNLOAD + version, Util.join(sheets, ","))
.apply();
}
public HashSet<String> 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();
}
}

View File

@@ -93,9 +93,10 @@ public class ApplicationMigrations {
//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;
}
public static final int CURRENT_VERSION = 51;
public static final int CURRENT_VERSION = 52;
/**
* This *must* be called after the {@link JobManager} has been instantiated, but *before* the call
@@ -401,6 +402,10 @@ public class ApplicationMigrations {
jobs.put(Version.PNI, new PniMigrationJob());
}
if (lastSeenVersion < Version.JUMBOMOJI_DOWNLOAD) {
jobs.put(Version.JUMBOMOJI_DOWNLOAD, new EmojiDownloadMigrationJob());
}
return jobs;
}

View File

@@ -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<EmojiDownloadMigrationJob> {
@Override
public @NonNull EmojiDownloadMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new EmojiDownloadMigrationJob(parameters);
}
}
}