From 7267d77dcb2e52ed09af5da1a2c41afc0fb485af Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Wed, 22 Sep 2021 10:42:13 -0400 Subject: [PATCH] Add support for syncing default reactions. --- .../securesms/keyvalue/EmojiValues.java | 28 +++++++++++++++++-- .../migrations/ApplicationMigrations.java | 7 ++++- .../StorageServiceMigrationJob.java | 9 ++++++ .../reactions/edit/EditReactionsViewModel.kt | 10 +++++++ .../storage/AccountRecordProcessor.java | 10 +++++-- .../securesms/storage/StorageSyncHelper.java | 2 ++ .../api/storage/SignalAccountRecord.java | 16 +++++++++++ ...gnalStorage.proto => StorageService.proto} | 1 + 8 files changed, 77 insertions(+), 6 deletions(-) rename libsignal/service/src/main/proto/{SignalStorage.proto => StorageService.proto} (98%) 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 57d33b585..cbf901814 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/EmojiValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/EmojiValues.java @@ -8,6 +8,7 @@ import androidx.annotation.Nullable; import org.thoughtcrime.securesms.components.emoji.EmojiUtil; import org.thoughtcrime.securesms.util.Util; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -68,16 +69,39 @@ public class EmojiValues extends SignalStoreValues { return getString(PREFIX + canonical, emoji); } + /** + * Returns a list usable emoji that the user has selected as their defaults. If any stored reactions are unreadable, it will provide a default. + * For raw access to the unfiltered list of reactions, see {@link #getRawReactions()}. + */ public @NonNull List getReactions() { + List raw = getRawReactions(); + List out = new ArrayList<>(DEFAULT_REACTIONS_LIST.size()); + + for (int i = 0; i < DEFAULT_REACTIONS_LIST.size(); i++) { + if (raw.size() > i && EmojiUtil.isEmoji(raw.get(i))) { + out.add(raw.get(i)); + } else { + out.add(DEFAULT_REACTIONS_LIST.get(i)); + } + } + + return out; + } + + /** + * A raw list of the default reactions the user has selected. It will be empty if there hasn't been any custom ones set. It may contain unrenderable emoji. + * This is primarily here for syncing to storage service. You probably want {@link #getReactions()} for everything else. + */ + public @NonNull List getRawReactions() { String list = getString(REACTIONS_LIST, ""); if (TextUtils.isEmpty(list)) { - return DEFAULT_REACTIONS_LIST; + return Collections.emptyList(); } else { return Arrays.asList(list.split(",")); } } - public void setReactions(List reactions) { + public void setReactions(@NonNull List reactions) { putString(REACTIONS_LIST, Util.join(reactions, ",")); } 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 727c3e9e7..13ccb6015 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java @@ -88,9 +88,10 @@ public class ApplicationMigrations { static final int CHANGE_NUMBER_SYNC = 44; static final int CHANGE_NUMBER_CAPABILITY = 45; static final int CHANGE_NUMBER_CAPABILITY_2 = 46; + static final int DEFAULT_REACTIONS_SYNC = 47; } - public static final int CURRENT_VERSION = 46; + public static final int CURRENT_VERSION = 47; /** * This *must* be called after the {@link JobManager} has been instantiated, but *before* the call @@ -384,6 +385,10 @@ public class ApplicationMigrations { jobs.put(Version.CHANGE_NUMBER_CAPABILITY_2, new AttributesMigrationJob()); } + if (lastSeenVersion < Version.DEFAULT_REACTIONS_SYNC) { + jobs.put(Version.DEFAULT_REACTIONS_SYNC, new StorageServiceMigrationJob()); + } + return jobs; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/StorageServiceMigrationJob.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/StorageServiceMigrationJob.java index da24ebe06..486301a97 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/StorageServiceMigrationJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/StorageServiceMigrationJob.java @@ -3,12 +3,14 @@ package org.thoughtcrime.securesms.migrations; import androidx.annotation.NonNull; import org.signal.core.util.logging.Log; +import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.jobs.MultiDeviceKeysUpdateJob; import org.thoughtcrime.securesms.jobs.StorageSyncJob; +import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.TextSecurePreferences; /** @@ -40,6 +42,13 @@ public class StorageServiceMigrationJob extends MigrationJob { @Override public void performMigration() { + if (TextSecurePreferences.getLocalUuid(context) == null) { + Log.w(TAG, "Self not yet available."); + return; + } + + DatabaseFactory.getRecipientDatabase(context).markNeedsSync(Recipient.self().getId()); + JobManager jobManager = ApplicationDependencies.getJobManager(); if (TextSecurePreferences.isMultiDevice(context)) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/edit/EditReactionsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/reactions/edit/EditReactionsViewModel.kt index 1fb0d01dd..dfa5c021d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/edit/EditReactionsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/edit/EditReactionsViewModel.kt @@ -2,8 +2,13 @@ package org.thoughtcrime.securesms.reactions.edit import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel +import org.signal.core.util.concurrent.SignalExecutors +import org.thoughtcrime.securesms.database.DatabaseFactory +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.keyvalue.EmojiValues import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.storage.StorageSyncHelper import org.thoughtcrime.securesms.util.livedata.LiveDataUtil import org.thoughtcrime.securesms.util.livedata.Store @@ -37,6 +42,11 @@ class EditReactionsViewModel : ViewModel() { fun save() { emojiValues.reactions = store.state.reactions + + SignalExecutors.BOUNDED.execute { + DatabaseFactory.getRecipientDatabase(ApplicationDependencies.getApplication()).markNeedsSync(Recipient.self().id) + StorageSyncHelper.scheduleSyncForDataChange() + } } companion object { diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/AccountRecordProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/storage/AccountRecordProcessor.java index bd431cdf3..82c55d6ac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/AccountRecordProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/AccountRecordProcessor.java @@ -98,8 +98,9 @@ public class AccountRecordProcessor extends DefaultStorageRecordProcessor defaultReactions = remote.getDefaultReactions().size() > 0 ? remote.getDefaultReactions() : local.getDefaultReactions(); + boolean matchesRemote = doParamsMatch(remote, unknownFields, givenName, familyName, avatarUrlPath, profileKey, noteToSelfArchived, noteToSelfForcedUnread, readReceipts, typingIndicators, sealedSenderIndicators, linkPreviews, phoneNumberSharingMode, unlisted, pinnedConversations, preferContactAvatars, payments, universalExpireTimer, primarySendsSms, e164, defaultReactions); + boolean matchesLocal = doParamsMatch(local, unknownFields, givenName, familyName, avatarUrlPath, profileKey, noteToSelfArchived, noteToSelfForcedUnread, readReceipts, typingIndicators, sealedSenderIndicators, linkPreviews, phoneNumberSharingMode, unlisted, pinnedConversations, preferContactAvatars, payments, universalExpireTimer, primarySendsSms, e164, defaultReactions); if (matchesRemote) { return remote; @@ -127,6 +128,7 @@ public class AccountRecordProcessor extends DefaultStorageRecordProcessor defaultReactions) { return Arrays.equals(contact.serializeUnknownFields(), unknownFields) && Objects.equals(contact.getGivenName().or(""), givenName) && @@ -173,6 +176,7 @@ public class AccountRecordProcessor extends DefaultStorageRecordProcessor profileKey; private final List pinnedConversations; private final Payments payments; + private final List defaultReactions; public SignalAccountRecord(StorageId id, AccountRecord proto) { this.id = id; @@ -44,6 +45,7 @@ public final class SignalAccountRecord implements SignalRecord { this.avatarUrlPath = OptionalUtil.absentIfEmpty(proto.getAvatarUrlPath()); this.pinnedConversations = new ArrayList<>(proto.getPinnedConversationsCount()); this.payments = new Payments(proto.getPayments().getEnabled(), OptionalUtil.absentIfEmpty(proto.getPayments().getEntropy())); + this.defaultReactions = new ArrayList<>(proto.getPreferredReactionEmojiList()); for (AccountRecord.PinnedConversation conversation : proto.getPinnedConversationsList()) { pinnedConversations.add(PinnedConversation.fromRemote(conversation)); @@ -142,6 +144,10 @@ public final class SignalAccountRecord implements SignalRecord { diff.add("E164"); } + if (!Objects.equals(this.getDefaultReactions(), that.getDefaultReactions())) { + diff.add("DefaultReactions"); + } + if (!Objects.equals(this.hasUnknownFields(), that.hasUnknownFields())) { diff.add("UnknownFields"); } @@ -232,6 +238,10 @@ public final class SignalAccountRecord implements SignalRecord { return proto.getE164(); } + public List getDefaultReactions() { + return defaultReactions; + } + AccountRecord toProto() { return proto; } @@ -501,6 +511,12 @@ public final class SignalAccountRecord implements SignalRecord { return this; } + public Builder setDefaultReactions(List defaultReactions) { + builder.clearPreferredReactionEmoji(); + builder.addAllPreferredReactionEmoji(defaultReactions); + return this; + } + public SignalAccountRecord build() { AccountRecord proto = builder.build(); diff --git a/libsignal/service/src/main/proto/SignalStorage.proto b/libsignal/service/src/main/proto/StorageService.proto similarity index 98% rename from libsignal/service/src/main/proto/SignalStorage.proto rename to libsignal/service/src/main/proto/StorageService.proto index a3ff4dd50..abfbfad0a 100644 --- a/libsignal/service/src/main/proto/SignalStorage.proto +++ b/libsignal/service/src/main/proto/StorageService.proto @@ -147,4 +147,5 @@ message AccountRecord { uint32 universalExpireTimer = 17; bool primarySendsSms = 18; string e164 = 19; + repeated string preferredReactionEmoji = 20; }