Sync mute status via storage service.

fork-5.53.8
Greyson Parrelli 2021-04-08 13:50:50 -04:00
rodzic 25ce2a649a
commit e096ba27ce
12 zmienionych plików z 158 dodań i 31 usunięć

Wyświetl plik

@ -1283,6 +1283,7 @@ public class RecipientDatabase extends Database {
values.put(USERNAME, TextUtils.isEmpty(username) ? null : username);
values.put(PROFILE_SHARING, contact.isProfileSharingEnabled() ? "1" : "0");
values.put(BLOCKED, contact.isBlocked() ? "1" : "0");
values.put(MUTE_UNTIL, contact.getMuteUntil());
values.put(STORAGE_SERVICE_ID, Base64.encodeBytes(contact.getId().getRaw()));
values.put(DIRTY, DirtyState.CLEAN.getId());
@ -1305,6 +1306,7 @@ public class RecipientDatabase extends Database {
values.put(GROUP_TYPE, GroupType.SIGNAL_V1.getId());
values.put(PROFILE_SHARING, groupV1.isProfileSharingEnabled() ? "1" : "0");
values.put(BLOCKED, groupV1.isBlocked() ? "1" : "0");
values.put(MUTE_UNTIL, groupV1.getMuteUntil());
values.put(STORAGE_SERVICE_ID, Base64.encodeBytes(groupV1.getId().getRaw()));
values.put(DIRTY, DirtyState.CLEAN.getId());
@ -1323,6 +1325,7 @@ public class RecipientDatabase extends Database {
values.put(GROUP_TYPE, GroupType.SIGNAL_V2.getId());
values.put(PROFILE_SHARING, groupV2.isProfileSharingEnabled() ? "1" : "0");
values.put(BLOCKED, groupV2.isBlocked() ? "1" : "0");
values.put(MUTE_UNTIL, groupV2.getMuteUntil());
values.put(STORAGE_SERVICE_ID, Base64.encodeBytes(groupV2.getId().getRaw()));
values.put(DIRTY, DirtyState.CLEAN.getId());
@ -1661,7 +1664,9 @@ public class RecipientDatabase extends Database {
values.put(MUTE_UNTIL, until);
if (update(id, values)) {
Recipient.live(id).refresh();
markDirty(id, DirtyState.UPDATE);
}
StorageSyncHelper.scheduleSyncForDataChange();
}
public void setSeenFirstInviteReminder(@NonNull RecipientId id) {

Wyświetl plik

@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.migrations.StorageServiceMigrationJob;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.storage.AccountRecordProcessor;
@ -25,6 +26,7 @@ import org.thoughtcrime.securesms.storage.ContactRecordProcessor;
import org.thoughtcrime.securesms.storage.GroupV1RecordProcessor;
import org.thoughtcrime.securesms.storage.GroupV2RecordProcessor;
import org.thoughtcrime.securesms.storage.StorageRecordProcessor;
import org.thoughtcrime.securesms.storage.StorageRecordUpdate;
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.storage.StorageSyncHelper.KeyDifferenceResult;
import org.thoughtcrime.securesms.storage.StorageSyncHelper.LocalWriteResult;
@ -42,6 +44,7 @@ import org.whispersystems.signalservice.api.storage.SignalAccountRecord;
import org.whispersystems.signalservice.api.storage.SignalContactRecord;
import org.whispersystems.signalservice.api.storage.SignalGroupV1Record;
import org.whispersystems.signalservice.api.storage.SignalGroupV2Record;
import org.whispersystems.signalservice.api.storage.SignalRecord;
import org.whispersystems.signalservice.api.storage.SignalStorageManifest;
import org.whispersystems.signalservice.api.storage.SignalStorageRecord;
import org.whispersystems.signalservice.api.storage.StorageId;
@ -107,6 +110,27 @@ import java.util.concurrent.TimeUnit;
* converting local data into a format that can be compared with, merged, and eventually written
* back to both local and remote data stores is tiresome. There's also lots of general bookkeeping,
* error handling, cleanup scenarios, logging, etc.
*
* == Syncing a new field on an existing record ==
*
* - Add the field the the respective proto
* - Update the respective model (i.e. {@link SignalContactRecord})
* - Add getters
* - Update the builder
* - Update {@link SignalRecord#describeDiff(SignalRecord)}.
* - Update the respective record processor (i.e {@link ContactRecordProcessor}). You need to make
* sure that you're:
* - Merging the attributes, likely preferring remote
* - Adding to doParamsMatch()
* - Adding the parameter to the builder chain when creating a merged model
* - Update builder usage in StorageSyncModels
* - Handle the new data when writing to the local storage
* (i.e. {@link RecipientDatabase#applyStorageSyncContactUpdate(StorageRecordUpdate)}).
* - Make sure that whenever you change the field in the UI, we mark the row as dirty and call
* {@link StorageSyncHelper#scheduleSyncForDataChange()}.
* - If you're syncing a field that was otherwise already present in the UI, you'll probably want
* to enqueue a {@link StorageServiceMigrationJob} as an app migration to make sure it gets
* synced.
*/
public class StorageSyncJobV2 extends BaseJob {

Wyświetl plik

@ -40,7 +40,7 @@ public class ApplicationMigrations {
private static final int LEGACY_CANONICAL_VERSION = 455;
public static final int CURRENT_VERSION = 30;
public static final int CURRENT_VERSION = 31;
private static final class Version {
static final int LEGACY = 1;
@ -71,6 +71,8 @@ public class ApplicationMigrations {
static final int DAY_BY_DAY_STICKERS = 26;
static final int BLOB_LOCATION = 27;
static final int SYSTEM_NAME_SPLIT = 28;
// Versions 29, 30 accidentally skipped
static final int MUTE_SYNC = 31;
}
/**
@ -301,6 +303,10 @@ public class ApplicationMigrations {
jobs.put(Version.SYSTEM_NAME_SPLIT, new DirectoryRefreshMigrationJob());
}
if (lastSeenVersion < Version.MUTE_SYNC) {
jobs.put(Version.MUTE_SYNC, new StorageServiceMigrationJob());
}
return jobs;
}

Wyświetl plik

@ -11,6 +11,9 @@ import org.thoughtcrime.securesms.jobs.MultiDeviceKeysUpdateJob;
import org.thoughtcrime.securesms.jobs.StorageSyncJob;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
/**
* Just runs a storage sync. Useful if you've started syncing a new field to storage service.
*/
public class StorageServiceMigrationJob extends MigrationJob {
private static final String TAG = Log.tag(StorageServiceMigrationJob.class);

Wyświetl plik

@ -97,8 +97,9 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
boolean profileSharing = remote.isProfileSharingEnabled();
boolean archived = remote.isArchived();
boolean forcedUnread = remote.isForcedUnread();
boolean matchesRemote = doParamsMatch(remote, unknownFields, address, givenName, familyName, profileKey, username, identityState, identityKey, blocked, profileSharing, archived, forcedUnread);
boolean matchesLocal = doParamsMatch(local, unknownFields, address, givenName, familyName, profileKey, username, identityState, identityKey, blocked, profileSharing, archived, forcedUnread);
long muteUntil = remote.getMuteUntil();
boolean matchesRemote = doParamsMatch(remote, unknownFields, address, givenName, familyName, profileKey, username, identityState, identityKey, blocked, profileSharing, archived, forcedUnread, muteUntil);
boolean matchesLocal = doParamsMatch(local, unknownFields, address, givenName, familyName, profileKey, username, identityState, identityKey, blocked, profileSharing, archived, forcedUnread, muteUntil);
if (matchesRemote) {
return remote;
@ -116,6 +117,7 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
.setBlocked(blocked)
.setProfileSharingEnabled(profileSharing)
.setForcedUnread(forcedUnread)
.setMuteUntil(muteUntil)
.build();
}
}
@ -154,7 +156,8 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
boolean blocked,
boolean profileSharing,
boolean archived,
boolean forcedUnread)
boolean forcedUnread,
long muteUntil)
{
return Arrays.equals(contact.serializeUnknownFields(), unknownFields) &&
Objects.equals(contact.getAddress(), address) &&
@ -167,6 +170,7 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
contact.isBlocked() == blocked &&
contact.isProfileSharingEnabled() == profileSharing &&
contact.isArchived() == archived &&
contact.isForcedUnread() == forcedUnread;
contact.isForcedUnread() == forcedUnread &&
contact.getMuteUntil() == muteUntil;
}
}

Wyświetl plik

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.storage;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.DatabaseFactory;
@ -80,9 +81,10 @@ public final class GroupV1RecordProcessor extends DefaultStorageRecordProcessor<
boolean profileSharing = remote.isProfileSharingEnabled();
boolean archived = remote.isArchived();
boolean forcedUnread = remote.isForcedUnread();
long muteUntil = remote.getMuteUntil();
boolean matchesRemote = Arrays.equals(unknownFields, remote.serializeUnknownFields()) && blocked == remote.isBlocked() && profileSharing == remote.isProfileSharingEnabled() && archived == remote.isArchived() && forcedUnread == remote.isForcedUnread();
boolean matchesLocal = Arrays.equals(unknownFields, local.serializeUnknownFields()) && blocked == local.isBlocked() && profileSharing == local.isProfileSharingEnabled() && archived == local.isArchived() && forcedUnread == local.isForcedUnread();
boolean matchesRemote = doParamsMatch(remote, unknownFields, blocked, profileSharing, archived, forcedUnread, muteUntil);
boolean matchesLocal = doParamsMatch(local, unknownFields, blocked, profileSharing, archived, forcedUnread, muteUntil);
if (matchesRemote) {
return remote;
@ -94,6 +96,7 @@ public final class GroupV1RecordProcessor extends DefaultStorageRecordProcessor<
.setBlocked(blocked)
.setProfileSharingEnabled(blocked)
.setForcedUnread(forcedUnread)
.setMuteUntil(muteUntil)
.build();
}
}
@ -117,4 +120,20 @@ public final class GroupV1RecordProcessor extends DefaultStorageRecordProcessor<
return lhs.getGroupId()[0] - rhs.getGroupId()[0];
}
}
private boolean doParamsMatch(@NonNull SignalGroupV1Record group,
@Nullable byte[] unknownFields,
boolean blocked,
boolean profileSharing,
boolean archived,
boolean forcedUnread,
long muteUntil)
{
return Arrays.equals(unknownFields, group.serializeUnknownFields()) &&
blocked == group.isBlocked() &&
profileSharing == group.isProfileSharingEnabled() &&
archived == group.isArchived() &&
forcedUnread == group.isForcedUnread() &&
muteUntil == group.getMuteUntil();
}
}

Wyświetl plik

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.storage;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.signal.zkgroup.groups.GroupMasterKey;
@ -15,6 +16,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.ParcelUtil;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.storage.SignalGroupV2Record;
import org.whispersystems.signalservice.internal.storage.protos.GroupV2Record;
import java.io.IOException;
import java.util.Arrays;
@ -61,9 +63,10 @@ public final class GroupV2RecordProcessor extends DefaultStorageRecordProcessor<
boolean profileSharing = remote.isProfileSharingEnabled();
boolean archived = remote.isArchived();
boolean forcedUnread = remote.isForcedUnread();
long muteUntil = remote.getMuteUntil();
boolean matchesRemote = Arrays.equals(unknownFields, remote.serializeUnknownFields()) && blocked == remote.isBlocked() && profileSharing == remote.isProfileSharingEnabled() && archived == remote.isArchived() && forcedUnread == remote.isForcedUnread();
boolean matchesLocal = Arrays.equals(unknownFields, local.serializeUnknownFields()) && blocked == local.isBlocked() && profileSharing == local.isProfileSharingEnabled() && archived == local.isArchived() && forcedUnread == local.isForcedUnread();
boolean matchesRemote = doParamsMatch(remote, unknownFields, blocked, profileSharing, archived, forcedUnread, muteUntil);
boolean matchesLocal = doParamsMatch(local, unknownFields, blocked, profileSharing, archived, forcedUnread, muteUntil);
if (matchesRemote) {
return remote;
@ -76,6 +79,7 @@ public final class GroupV2RecordProcessor extends DefaultStorageRecordProcessor<
.setProfileSharingEnabled(blocked)
.setArchived(archived)
.setForcedUnread(forcedUnread)
.setMuteUntil(muteUntil)
.build();
}
}
@ -116,4 +120,20 @@ public final class GroupV2RecordProcessor extends DefaultStorageRecordProcessor<
return lhs.getMasterKeyBytes()[0] - rhs.getMasterKeyBytes()[0];
}
}
private boolean doParamsMatch(@NonNull SignalGroupV2Record group,
@Nullable byte[] unknownFields,
boolean blocked,
boolean profileSharing,
boolean archived,
boolean forcedUnread,
long muteUntil)
{
return Arrays.equals(unknownFields, group.serializeUnknownFields()) &&
blocked == group.isBlocked() &&
profileSharing == group.isProfileSharingEnabled() &&
archived == group.isArchived() &&
forcedUnread == group.isForcedUnread() &&
muteUntil == group.getMuteUntil();
}
}

Wyświetl plik

@ -94,6 +94,7 @@ public final class StorageSyncModels {
.setIdentityState(localToRemoteIdentityState(recipient.getSyncExtras().getIdentityStatus()))
.setArchived(recipient.getSyncExtras().isArchived())
.setForcedUnread(recipient.getSyncExtras().isForcedUnread())
.setMuteUntil(recipient.getMuteUntil())
.build();
}
@ -114,6 +115,7 @@ public final class StorageSyncModels {
.setProfileSharingEnabled(recipient.isProfileSharing())
.setArchived(recipient.getSyncExtras().isArchived())
.setForcedUnread(recipient.getSyncExtras().isForcedUnread())
.setMuteUntil(recipient.getMuteUntil())
.build();
}
@ -140,6 +142,7 @@ public final class StorageSyncModels {
.setProfileSharingEnabled(recipient.isProfileSharing())
.setArchived(recipient.getSyncExtras().isArchived())
.setForcedUnread(recipient.getSyncExtras().isForcedUnread())
.setMuteUntil(recipient.getMuteUntil())
.build();
}

Wyświetl plik

@ -104,6 +104,10 @@ public final class SignalContactRecord implements SignalRecord {
diff.add("ForcedUnread");
}
if (!Objects.equals(this.getMuteUntil(), that.getMuteUntil())) {
diff.add("MuteUntil");
}
if (!Objects.equals(this.hasUnknownFields(), that.hasUnknownFields())) {
diff.add("UnknownFields");
}
@ -166,6 +170,10 @@ public final class SignalContactRecord implements SignalRecord {
return proto.getMarkedUnread();
}
public long getMuteUntil() {
return proto.getMutedUntilTimestamp();
}
ContactRecord toProto() {
return proto;
}
@ -253,6 +261,11 @@ public final class SignalContactRecord implements SignalRecord {
return this;
}
public Builder setMuteUntil(long muteUntil) {
builder.setMutedUntilTimestamp(muteUntil);
return this;
}
public SignalContactRecord build() {
ContactRecord proto = builder.build();

Wyświetl plik

@ -60,6 +60,10 @@ public final class SignalGroupV1Record implements SignalRecord {
diff.add("ForcedUnread");
}
if (!Objects.equals(this.getMuteUntil(), that.getMuteUntil())) {
diff.add("MuteUntil");
}
if (!Objects.equals(this.hasUnknownFields(), that.hasUnknownFields())) {
diff.add("UnknownFields");
}
@ -98,6 +102,10 @@ public final class SignalGroupV1Record implements SignalRecord {
return proto.getMarkedUnread();
}
public long getMuteUntil() {
return proto.getMutedUntilTimestamp();
}
GroupV1Record toProto() {
return proto;
}
@ -154,6 +162,11 @@ public final class SignalGroupV1Record implements SignalRecord {
return this;
}
public Builder setMuteUntil(long muteUntil) {
builder.setMutedUntilTimestamp(muteUntil);
return this;
}
public SignalGroupV1Record build() {
GroupV1Record proto = builder.build();

Wyświetl plik

@ -62,6 +62,10 @@ public final class SignalGroupV2Record implements SignalRecord {
diff.add("ForcedUnread");
}
if (!Objects.equals(this.getMuteUntil(), that.getMuteUntil())) {
diff.add("MuteUntil");
}
if (!Objects.equals(this.hasUnknownFields(), that.hasUnknownFields())) {
diff.add("UnknownFields");
}
@ -108,6 +112,11 @@ public final class SignalGroupV2Record implements SignalRecord {
return proto.getMarkedUnread();
}
public long getMuteUntil() {
return proto.getMutedUntilTimestamp();
}
GroupV2Record toProto() {
return proto;
}
@ -168,6 +177,11 @@ public final class SignalGroupV2Record implements SignalRecord {
return this;
}
public Builder setMuteUntil(long muteUntil) {
builder.setMutedUntilTimestamp(muteUntil);
return this;
}
public SignalGroupV2Record build() {
GroupV2Record proto = builder.build();

Wyświetl plik

@ -69,34 +69,37 @@ message ContactRecord {
UNVERIFIED = 2;
}
string serviceUuid = 1;
string serviceE164 = 2;
bytes profileKey = 3;
bytes identityKey = 4;
IdentityState identityState = 5;
string givenName = 6;
string familyName = 7;
string username = 8;
bool blocked = 9;
bool whitelisted = 10;
bool archived = 11;
bool markedUnread = 12;
string serviceUuid = 1;
string serviceE164 = 2;
bytes profileKey = 3;
bytes identityKey = 4;
IdentityState identityState = 5;
string givenName = 6;
string familyName = 7;
string username = 8;
bool blocked = 9;
bool whitelisted = 10;
bool archived = 11;
bool markedUnread = 12;
uint64 mutedUntilTimestamp = 13;
}
message GroupV1Record {
bytes id = 1;
bool blocked = 2;
bool whitelisted = 3;
bool archived = 4;
bool markedUnread = 5;
bytes id = 1;
bool blocked = 2;
bool whitelisted = 3;
bool archived = 4;
bool markedUnread = 5;
uint64 mutedUntilTimestamp = 6;
}
message GroupV2Record {
bytes masterKey = 1;
bool blocked = 2;
bool whitelisted = 3;
bool archived = 4;
bool markedUnread = 5;
bytes masterKey = 1;
bool blocked = 2;
bool whitelisted = 3;
bool archived = 4;
bool markedUnread = 5;
uint64 mutedUntilTimestamp = 6;
}
message Payments {