Add support for syncing forced unread status.

master
Greyson Parrelli 2020-10-07 13:32:59 -04:00
parent ed0be6fc9a
commit 63746bbb47
19 changed files with 274 additions and 66 deletions

View File

@ -668,6 +668,20 @@ public class RecipientDatabase extends Database {
return null;
}
public void markNeedsSync(@NonNull Collection<RecipientId> recipientIds) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.beginTransaction();
try {
for (RecipientId recipientId : recipientIds) {
markDirty(recipientId, DirtyState.UPDATE);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
public void markNeedsSync(@NonNull RecipientId recipientId) {
markDirty(recipientId, DirtyState.UPDATE);
}
@ -768,7 +782,7 @@ public class RecipientDatabase extends Database {
}
}
threadDatabase.setArchived(recipientId, insert.isArchived());
threadDatabase.applyStorageSyncUpdate(recipientId, insert);
needsRefresh.add(recipientId);
}
@ -821,7 +835,7 @@ public class RecipientDatabase extends Database {
Log.w(TAG, "Failed to process identity key during update! Skipping.", e);
}
threadDatabase.setArchived(recipientId, update.getNew().isArchived());
threadDatabase.applyStorageSyncUpdate(recipientId, update.getNew());
needsRefresh.add(recipientId);
}
@ -830,7 +844,7 @@ public class RecipientDatabase extends Database {
Recipient recipient = Recipient.externalGroup(context, GroupId.v1orThrow(insert.getGroupId()));
threadDatabase.setArchived(recipient.getId(), insert.isArchived());
threadDatabase.applyStorageSyncUpdate(recipient.getId(), insert);
needsRefresh.add(recipient.getId());
}
@ -844,7 +858,7 @@ public class RecipientDatabase extends Database {
Recipient recipient = Recipient.externalGroup(context, GroupId.v1orThrow(update.getOld().getGroupId()));
threadDatabase.setArchived(recipient.getId(), update.getNew().isArchived());
threadDatabase.applyStorageSyncUpdate(recipient.getId(), update.getNew());
needsRefresh.add(recipient.getId());
}
@ -872,7 +886,7 @@ public class RecipientDatabase extends Database {
ApplicationDependencies.getJobManager().add(new RequestGroupV2InfoJob(groupId));
threadDatabase.setArchived(recipient.getId(), insert.isArchived());
threadDatabase.applyStorageSyncUpdate(recipient.getId(), insert);
needsRefresh.add(recipient.getId());
}
@ -887,7 +901,7 @@ public class RecipientDatabase extends Database {
GroupMasterKey masterKey = update.getOld().getMasterKeyOrThrow();
Recipient recipient = Recipient.externalGroup(context, GroupId.v2(masterKey));
threadDatabase.setArchived(recipient.getId(), update.getNew().isArchived());
threadDatabase.applyStorageSyncUpdate(recipient.getId(), update.getNew());
needsRefresh.add(recipient.getId());
}
@ -936,6 +950,8 @@ public class RecipientDatabase extends Database {
ApplicationDependencies.getJobManager().add(new RefreshAttributesJob());
}
DatabaseFactory.getThreadDatabase(context).applyStorageSyncUpdate(Recipient.self().getId(), update);
Recipient.self().live().refresh();
}
@ -1236,12 +1252,13 @@ public class RecipientDatabase extends Database {
String storageProtoRaw = CursorUtil.getString(cursor, STORAGE_PROTO).orNull();
byte[] storageProto = storageProtoRaw != null ? Base64.decodeOrThrow(storageProtoRaw) : null;
boolean archived = CursorUtil.getBoolean(cursor, ThreadDatabase.ARCHIVED).or(false);
boolean forcedUnread = CursorUtil.getInt(cursor, ThreadDatabase.READ).transform(status -> status == ThreadDatabase.ReadStatus.FORCED_UNREAD.serialize()).or(false);
GroupMasterKey groupMasterKey = CursorUtil.getBlob(cursor, GroupDatabase.V2_MASTER_KEY).transform(GroupUtil::requireMasterKey).orNull();
byte[] identityKey = CursorUtil.getString(cursor, IDENTITY_KEY).transform(Base64::decodeOrThrow).orNull();
VerifiedStatus identityStatus = CursorUtil.getInt(cursor, IDENTITY_STATUS).transform(VerifiedStatus::forState).or(VerifiedStatus.DEFAULT);
return new RecipientSettings.SyncExtras(storageProto, groupMasterKey, identityKey, identityStatus, archived);
return new RecipientSettings.SyncExtras(storageProto, groupMasterKey, identityKey, identityStatus, archived, forcedUnread);
}
public BulkOperationsHandle beginBulkSystemContactUpdate() {
@ -1422,7 +1439,7 @@ public class RecipientDatabase extends Database {
valuesToSet.putNull(PROFILE_KEY_CREDENTIAL);
valuesToSet.put(UNIDENTIFIED_ACCESS_MODE, UnidentifiedAccessMode.UNKNOWN.getMode());
SqlUtil.UpdateQuery updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, valuesToCompare);
SqlUtil.Query updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, valuesToCompare);
if (update(updateQuery, valuesToSet)) {
markDirty(id, DirtyState.UPDATE);
@ -1471,7 +1488,7 @@ public class RecipientDatabase extends Database {
values.put(PROFILE_KEY_CREDENTIAL, Base64.encodeBytes(profileKeyCredential.serialize()));
SqlUtil.UpdateQuery updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, values);
SqlUtil.Query updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, values);
if (update(updateQuery, values)) {
// TODO [greyson] If we sync this in future, mark dirty
@ -2250,9 +2267,7 @@ public class RecipientDatabase extends Database {
* query such that this will only return true if a row was *actually* updated.
*/
private boolean update(@NonNull RecipientId id, @NonNull ContentValues contentValues) {
String selection = ID + " = ?";
String[] args = new String[]{id.serialize()};
SqlUtil.UpdateQuery updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, contentValues);
SqlUtil.Query updateQuery = SqlUtil.buildTrueUpdateQuery(ID_WHERE, SqlUtil.buildArgs(id), contentValues);
return update(updateQuery, contentValues);
}
@ -2262,7 +2277,7 @@ public class RecipientDatabase extends Database {
* <p>
* This will only return true if a row was *actually* updated with respect to the where clause of the {@param updateQuery}.
*/
private boolean update(@NonNull SqlUtil.UpdateQuery updateQuery, @NonNull ContentValues contentValues) {
private boolean update(@NonNull SqlUtil.Query updateQuery, @NonNull ContentValues contentValues) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
return database.update(TABLE_NAME, contentValues, updateQuery.getWhere(), updateQuery.getWhereArgs()) > 0;
@ -2816,18 +2831,21 @@ public class RecipientDatabase extends Database {
private final byte[] identityKey;
private final VerifiedStatus identityStatus;
private final boolean archived;
private final boolean forcedUnread;
public SyncExtras(@Nullable byte[] storageProto,
@Nullable GroupMasterKey groupMasterKey,
@Nullable byte[] identityKey,
@NonNull VerifiedStatus identityStatus,
boolean archived)
boolean archived,
boolean forcedUnread)
{
this.storageProto = storageProto;
this.groupMasterKey = groupMasterKey;
this.identityKey = identityKey;
this.identityStatus = identityStatus;
this.archived = archived;
this.forcedUnread = forcedUnread;
}
public @Nullable byte[] getStorageProto() {
@ -2849,6 +2867,10 @@ public class RecipientDatabase extends Database {
public @NonNull VerifiedStatus getIdentityStatus() {
return identityStatus;
}
public boolean isForcedUnread() {
return forcedUnread;
}
}
}

View File

@ -32,8 +32,6 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import net.sqlcipher.database.SQLiteDatabase;
import org.jsoup.helper.StringUtil;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedMember;
import org.thoughtcrime.securesms.database.MessageDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
@ -56,11 +54,15 @@ import org.thoughtcrime.securesms.util.SqlUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
import org.whispersystems.signalservice.api.util.UuidUtil;
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.SignalStorageRecord;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@ -70,7 +72,6 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
public class ThreadDatabase extends Database {
@ -102,7 +103,7 @@ public class ThreadDatabase extends Database {
public static final String LAST_SEEN = "last_seen";
public static final String HAS_SENT = "has_sent";
private static final String LAST_SCROLLED = "last_scrolled";
private static final String PINNED = "pinned";
static final String PINNED = "pinned";
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
DATE + " INTEGER DEFAULT 0, " +
@ -405,6 +406,7 @@ public class ThreadDatabase extends Database {
List<MarkedMessageInfo> smsRecords = new LinkedList<>();
List<MarkedMessageInfo> mmsRecords = new LinkedList<>();
boolean needsSync = false;
db.beginTransaction();
@ -417,6 +419,8 @@ public class ThreadDatabase extends Database {
}
for (long threadId : threadIds) {
ThreadRecord previous = getThreadRecord(threadId);
smsRecords.addAll(DatabaseFactory.getSmsDatabase(context).setMessagesReadSince(threadId, sinceTimestamp));
mmsRecords.addAll(DatabaseFactory.getMmsDatabase(context).setMessagesReadSince(threadId, sinceTimestamp));
@ -427,7 +431,12 @@ public class ThreadDatabase extends Database {
contentValues.put(UNREAD_COUNT, unreadCount);
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[]{threadId + ""});
db.update(TABLE_NAME, contentValues, ID_WHERE, SqlUtil.buildArgs(threadId));
if (previous != null && previous.isForcedUnread()) {
DatabaseFactory.getRecipientDatabase(context).markNeedsSync(previous.getRecipient().getId());
needsSync = true;
}
}
db.setTransactionSuccessful();
@ -437,6 +446,11 @@ public class ThreadDatabase extends Database {
notifyConversationListeners(new HashSet<>(threadIds));
notifyConversationListListeners();
if (needsSync) {
StorageSyncHelper.scheduleSyncForDataChange();
}
return Util.concatenatedList(smsRecords, mmsRecords);
}
@ -445,19 +459,22 @@ public class ThreadDatabase extends Database {
db.beginTransaction();
try {
ContentValues contentValues = new ContentValues();
List<RecipientId> recipientIds = getRecipientIdsForThreadIds(threadIds);
SqlUtil.Query query = SqlUtil.buildCollectionQuery(ID, threadIds);
ContentValues contentValues = new ContentValues();
contentValues.put(READ, ReadStatus.FORCED_UNREAD.serialize());
for (long threadId : threadIds) {
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] { String.valueOf(threadId) });
}
db.update(TABLE_NAME, contentValues, query.getWhere(), query.getWhereArgs());
DatabaseFactory.getRecipientDatabase(context).markNeedsSync(recipientIds);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
notifyConversationListListeners();
StorageSyncHelper.scheduleSyncForDataChange();
notifyConversationListListeners();
}
}
@ -949,6 +966,20 @@ public class ThreadDatabase extends Database {
return Recipient.resolved(id);
}
public @NonNull List<RecipientId> getRecipientIdsForThreadIds(Collection<Long> threadIds) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
SqlUtil.Query query = SqlUtil.buildCollectionQuery(ID, threadIds);
List<RecipientId> ids = new ArrayList<>(threadIds.size());
try (Cursor cursor = db.query(TABLE_NAME, new String[] { RECIPIENT_ID }, query.getWhere(), query.getWhereArgs(), null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
ids.add(RecipientId.from(CursorUtil.requireLong(cursor, RECIPIENT_ID)));
}
}
return ids;
}
public boolean hasThread(@NonNull RecipientId recipientId) {
return getThreadIdIfExistsFor(recipientId) > -1;
}
@ -964,16 +995,56 @@ public class ThreadDatabase extends Database {
}
void updateReadState(long threadId) {
int unreadCount = DatabaseFactory.getMmsSmsDatabase(context).getUnreadCount(threadId);
ThreadRecord previous = getThreadRecord(threadId);
int unreadCount = DatabaseFactory.getMmsSmsDatabase(context).getUnreadCount(threadId);
ContentValues contentValues = new ContentValues();
contentValues.put(READ, unreadCount == 0);
contentValues.put(READ, unreadCount == 0 ? ReadStatus.READ.serialize() : ReadStatus.UNREAD.serialize());
contentValues.put(UNREAD_COUNT, unreadCount);
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues,ID_WHERE,
new String[] {String.valueOf(threadId)});
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, ID_WHERE, SqlUtil.buildArgs(threadId));
notifyConversationListListeners();
if (previous != null && previous.isForcedUnread()) {
DatabaseFactory.getRecipientDatabase(context).markNeedsSync(previous.getRecipient().getId());
StorageSyncHelper.scheduleSyncForDataChange();
}
}
public void applyStorageSyncUpdate(@NonNull RecipientId recipientId, @NonNull SignalContactRecord record) {
applyStorageSyncUpdate(recipientId, record.isArchived(), record.isForcedUnread());
}
public void applyStorageSyncUpdate(@NonNull RecipientId recipientId, @NonNull SignalGroupV1Record record) {
applyStorageSyncUpdate(recipientId, record.isArchived(), record.isForcedUnread());
}
public void applyStorageSyncUpdate(@NonNull RecipientId recipientId, @NonNull SignalGroupV2Record record) {
applyStorageSyncUpdate(recipientId, record.isArchived(), record.isForcedUnread());
}
public void applyStorageSyncUpdate(@NonNull RecipientId recipientId, @NonNull SignalAccountRecord record) {
applyStorageSyncUpdate(recipientId, record.isNoteToSelfArchived(), record.isNoteToSelfForcedUnread());
}
private void applyStorageSyncUpdate(@NonNull RecipientId recipientId, boolean archived, boolean forcedUnread) {
ContentValues values = new ContentValues();
values.put(ARCHIVED, archived);
if (forcedUnread) {
values.put(READ, ReadStatus.FORCED_UNREAD.serialize());
} else {
Long threadId = getThreadIdFor(recipientId);
if (threadId != null) {
int unreadCount = DatabaseFactory.getMmsSmsDatabase(context).getUnreadCount(threadId);
values.put(READ, unreadCount == 0 ? ReadStatus.READ.serialize() : ReadStatus.UNREAD.serialize());
values.put(UNREAD_COUNT, unreadCount);
}
}
databaseHelper.getWritableDatabase().update(TABLE_NAME, values, RECIPIENT_ID + " = ?", SqlUtil.buildArgs(recipientId));
}
public boolean update(long threadId, boolean unarchive) {
@ -1404,7 +1475,7 @@ public class ThreadDatabase extends Database {
}
}
private enum ReadStatus {
enum ReadStatus {
READ(1), UNREAD(0), FORCED_UNREAD(2);
private final int value;

View File

@ -183,6 +183,8 @@ public class StorageSyncJob extends BaseJob {
}
remoteManifestVersion = writeOperationResult.getManifest().getVersion();
needsMultiDeviceSync = true;
} else {
Log.i(TAG, "[Remote Newer] After resolving the conflict, all changes are local. No remote writes needed.");
}
@ -190,7 +192,6 @@ public class StorageSyncJob extends BaseJob {
recipientDatabase.applyStorageSyncUpdates(mergeResult.getLocalContactInserts(), mergeResult.getLocalContactUpdates(), mergeResult.getLocalGroupV1Inserts(), mergeResult.getLocalGroupV1Updates(), mergeResult.getLocalGroupV2Inserts(), mergeResult.getLocalGroupV2Updates());
storageKeyDatabase.applyStorageSyncUpdates(mergeResult.getLocalUnknownInserts(), mergeResult.getLocalUnknownDeletes());
StorageSyncHelper.applyAccountStorageSyncUpdates(context, mergeResult.getLocalAccountUpdate());
needsMultiDeviceSync = true;
Log.i(TAG, "[Remote Newer] Updating local manifest version to: " + remoteManifestVersion);
TextSecurePreferences.setStorageManifestVersion(context, remoteManifestVersion);

View File

@ -60,14 +60,15 @@ class AccountConflictMerger implements StorageSyncHelper.ConflictMerger<SignalAc
String avatarUrlPath = remote.getAvatarUrlPath().or(local.getAvatarUrlPath()).or("");
byte[] profileKey = remote.getProfileKey().or(local.getProfileKey()).orNull();
boolean noteToSelfArchived = remote.isNoteToSelfArchived();
boolean noteToSelfForcedUnread = remote.isNoteToSelfForcedUnread();
boolean readReceipts = remote.isReadReceiptsEnabled();
boolean typingIndicators = remote.isTypingIndicatorsEnabled();
boolean sealedSenderIndicators = remote.isSealedSenderIndicatorsEnabled();
boolean linkPreviews = remote.isLinkPreviewsEnabled();
boolean unlisted = remote.isPhoneNumberUnlisted();
AccountRecord.PhoneNumberSharingMode phoneNumberSharingMode = remote.getPhoneNumberSharingMode();
boolean matchesRemote = doParamsMatch(remote, unknownFields, givenName, familyName, avatarUrlPath, profileKey, noteToSelfArchived, readReceipts, typingIndicators, sealedSenderIndicators, linkPreviews, phoneNumberSharingMode, unlisted);
boolean matchesLocal = doParamsMatch(local, unknownFields, givenName, familyName, avatarUrlPath, profileKey, noteToSelfArchived, readReceipts, typingIndicators, sealedSenderIndicators, linkPreviews, phoneNumberSharingMode, unlisted );
boolean matchesRemote = doParamsMatch(remote, unknownFields, givenName, familyName, avatarUrlPath, profileKey, noteToSelfArchived, noteToSelfForcedUnread, readReceipts, typingIndicators, sealedSenderIndicators, linkPreviews, phoneNumberSharingMode, unlisted);
boolean matchesLocal = doParamsMatch(local, unknownFields, givenName, familyName, avatarUrlPath, profileKey, noteToSelfArchived, noteToSelfForcedUnread, readReceipts, typingIndicators, sealedSenderIndicators, linkPreviews, phoneNumberSharingMode, unlisted );
if (matchesRemote) {
return remote;
@ -81,6 +82,7 @@ class AccountConflictMerger implements StorageSyncHelper.ConflictMerger<SignalAc
.setAvatarUrlPath(avatarUrlPath)
.setProfileKey(profileKey)
.setNoteToSelfArchived(noteToSelfArchived)
.setNoteToSelfForcedUnread(noteToSelfForcedUnread)
.setReadReceiptsEnabled(readReceipts)
.setTypingIndicatorsEnabled(typingIndicators)
.setSealedSenderIndicatorsEnabled(sealedSenderIndicators)
@ -99,6 +101,7 @@ class AccountConflictMerger implements StorageSyncHelper.ConflictMerger<SignalAc
@NonNull String avatarUrlPath,
@Nullable byte[] profileKey,
boolean noteToSelfArchived,
boolean noteToSelfForcedUnread,
boolean readReceipts,
boolean typingIndicators,
boolean sealedSenderIndicators,
@ -112,6 +115,7 @@ class AccountConflictMerger implements StorageSyncHelper.ConflictMerger<SignalAc
Objects.equals(contact.getAvatarUrlPath().or(""), avatarUrlPath) &&
Arrays.equals(contact.getProfileKey().orNull(), profileKey) &&
contact.isNoteToSelfArchived() == noteToSelfArchived &&
contact.isNoteToSelfForcedUnread() == noteToSelfForcedUnread &&
contact.isReadReceiptsEnabled() == readReceipts &&
contact.isTypingIndicatorsEnabled() == typingIndicators &&
contact.isSealedSenderIndicatorsEnabled() == sealedSenderIndicators &&

View File

@ -86,8 +86,9 @@ class ContactConflictMerger implements StorageSyncHelper.ConflictMerger<SignalCo
boolean blocked = remote.isBlocked();
boolean profileSharing = remote.isProfileSharingEnabled();
boolean archived = remote.isArchived();
boolean matchesRemote = doParamsMatch(remote, unknownFields, address, givenName, familyName, profileKey, username, identityState, identityKey, blocked, profileSharing, archived);
boolean matchesLocal = doParamsMatch(local, unknownFields, address, givenName, familyName, profileKey, username, identityState, identityKey, blocked, profileSharing, archived);
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);
if (matchesRemote) {
return remote;
@ -104,6 +105,7 @@ class ContactConflictMerger implements StorageSyncHelper.ConflictMerger<SignalCo
.setIdentityKey(identityKey)
.setBlocked(blocked)
.setProfileSharingEnabled(profileSharing)
.setForcedUnread(forcedUnread)
.build();
}
}
@ -119,7 +121,8 @@ class ContactConflictMerger implements StorageSyncHelper.ConflictMerger<SignalCo
@Nullable byte[] identityKey,
boolean blocked,
boolean profileSharing,
boolean archived)
boolean archived,
boolean forcedUnread)
{
return Arrays.equals(contact.serializeUnknownFields(), unknownFields) &&
Objects.equals(contact.getAddress(), address) &&
@ -131,6 +134,7 @@ class ContactConflictMerger implements StorageSyncHelper.ConflictMerger<SignalCo
Arrays.equals(contact.getIdentityKey().orNull(), identityKey) &&
contact.isBlocked() == blocked &&
contact.isProfileSharingEnabled() == profileSharing &&
contact.isArchived() == archived;
contact.isArchived() == archived &&
contact.isForcedUnread() == forcedUnread;
}
}

View File

@ -40,9 +40,10 @@ final class GroupV1ConflictMerger implements StorageSyncHelper.ConflictMerger<Si
boolean blocked = remote.isBlocked();
boolean profileSharing = remote.isProfileSharingEnabled();
boolean archived = remote.isArchived();
boolean forcedUnread = remote.isForcedUnread();
boolean matchesRemote = Arrays.equals(unknownFields, remote.serializeUnknownFields()) && blocked == remote.isBlocked() && profileSharing == remote.isProfileSharingEnabled() && archived == remote.isArchived();
boolean matchesLocal = Arrays.equals(unknownFields, local.serializeUnknownFields()) && blocked == local.isBlocked() && profileSharing == local.isProfileSharingEnabled() && archived == local.isArchived();
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();
if (matchesRemote) {
return remote;
@ -53,6 +54,7 @@ final class GroupV1ConflictMerger implements StorageSyncHelper.ConflictMerger<Si
.setUnknownFields(unknownFields)
.setBlocked(blocked)
.setProfileSharingEnabled(blocked)
.setForcedUnread(forcedUnread)
.build();
}
}

View File

@ -40,9 +40,10 @@ final class GroupV2ConflictMerger implements StorageSyncHelper.ConflictMerger<Si
boolean blocked = remote.isBlocked();
boolean profileSharing = remote.isProfileSharingEnabled();
boolean archived = remote.isArchived();
boolean forcedUnread = remote.isForcedUnread();
boolean matchesRemote = Arrays.equals(unknownFields, remote.serializeUnknownFields()) && blocked == remote.isBlocked() && profileSharing == remote.isProfileSharingEnabled() && archived == remote.isArchived();
boolean matchesLocal = Arrays.equals(unknownFields, local.serializeUnknownFields()) && blocked == local.isBlocked() && profileSharing == local.isProfileSharingEnabled() && archived == local.isArchived();
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();
if (matchesRemote) {
return remote;
@ -53,6 +54,8 @@ final class GroupV2ConflictMerger implements StorageSyncHelper.ConflictMerger<Si
.setUnknownFields(unknownFields)
.setBlocked(blocked)
.setProfileSharingEnabled(blocked)
.setArchived(archived)
.setForcedUnread(forcedUnread)
.build();
}
}

View File

@ -12,6 +12,7 @@ import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob;
import org.thoughtcrime.securesms.jobs.StorageSyncJob;
@ -417,7 +418,8 @@ public final class StorageSyncHelper {
.setGivenName(self.getProfileName().getGivenName())
.setFamilyName(self.getProfileName().getFamilyName())
.setAvatarUrlPath(self.getProfileAvatar())
.setNoteToSelfArchived(DatabaseFactory.getThreadDatabase(context).isArchived(self.getId()))
.setNoteToSelfArchived(settings != null && settings.getSyncExtras().isArchived())
.setNoteToSelfForcedUnread(settings != null && settings.getSyncExtras().isForcedUnread())
.setTypingIndicatorsEnabled(TextSecurePreferences.isTypingIndicatorsEnabled(context))
.setReadReceiptsEnabled(TextSecurePreferences.isReadReceiptsEnabled(context))
.setSealedSenderIndicatorsEnabled(TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(context))
@ -447,7 +449,6 @@ public final class StorageSyncHelper {
public static void applyAccountStorageSyncUpdates(@NonNull Context context, @NonNull StorageId storageId, @NonNull SignalAccountRecord update, boolean fetchProfile) {
DatabaseFactory.getRecipientDatabase(context).applyStorageSyncUpdates(storageId, update);
DatabaseFactory.getThreadDatabase(context).setArchived(Recipient.self().getId(), update.isNoteToSelfArchived());
TextSecurePreferences.setReadReceiptsEnabled(context, update.isReadReceiptsEnabled());
TextSecurePreferences.setTypingIndicatorsEnabled(context, update.isTypingIndicatorsEnabled());

View File

@ -52,6 +52,7 @@ public final class StorageSyncModels {
.setIdentityKey(recipient.getSyncExtras().getIdentityKey())
.setIdentityState(localToRemoteIdentityState(recipient.getSyncExtras().getIdentityStatus()))
.setArchived(recipient.getSyncExtras().isArchived())
.setForcedUnread(recipient.getSyncExtras().isForcedUnread())
.build();
}
@ -71,6 +72,7 @@ public final class StorageSyncModels {
.setBlocked(recipient.isBlocked())
.setProfileSharingEnabled(recipient.isProfileSharing())
.setArchived(recipient.getSyncExtras().isArchived())
.setForcedUnread(recipient.getSyncExtras().isForcedUnread())
.build();
}
@ -96,6 +98,7 @@ public final class StorageSyncModels {
.setBlocked(recipient.isBlocked())
.setProfileSharingEnabled(recipient.isProfileSharing())
.setArchived(recipient.getSyncExtras().isArchived())
.setForcedUnread(recipient.getSyncExtras().isForcedUnread())
.build();
}

View File

@ -7,11 +7,13 @@ import androidx.annotation.NonNull;
import net.sqlcipher.database.SQLiteDatabase;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.whispersystems.libsignal.util.guava.Preconditions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -63,9 +65,9 @@ public final class SqlUtil {
* change. In other words, if {@link SQLiteDatabase#update(String, ContentValues, String, String[])}
* returns > 0, then you know something *actually* changed.
*/
public static @NonNull UpdateQuery buildTrueUpdateQuery(@NonNull String selection,
@NonNull String[] args,
@NonNull ContentValues contentValues)
public static @NonNull Query buildTrueUpdateQuery(@NonNull String selection,
@NonNull String[] args,
@NonNull ContentValues contentValues)
{
StringBuilder qualifier = new StringBuilder();
Set<Map.Entry<String, Object>> valueSet = contentValues.valueSet();
@ -90,7 +92,29 @@ public final class SqlUtil {
i++;
}
return new UpdateQuery("(" + selection + ") AND (" + qualifier + ")", fullArgs.toArray(new String[0]));
return new Query("(" + selection + ") AND (" + qualifier + ")", fullArgs.toArray(new String[0]));
}
public static @NonNull Query buildCollectionQuery(@NonNull String column, @NonNull Collection<? extends Object> values) {
Preconditions.checkArgument(values.size() > 0);
StringBuilder query = new StringBuilder();
Object[] args = new Object[values.size()];
int i = 0;
for (Object value : values) {
query.append("?");
args[i] = value;
if (i != values.size() - 1) {
query.append(", ");
}
i++;
}
return new Query(column + " IN (" + query.toString() + ")", buildArgs(args));
}
public static String[] appendArg(@NonNull String[] args, String addition) {
@ -102,11 +126,11 @@ public final class SqlUtil {
return output;
}
public static class UpdateQuery {
public static class Query {
private final String where;
private final String[] whereArgs;
private UpdateQuery(@NonNull String where, @NonNull String[] whereArgs) {
private Query(@NonNull String where, @NonNull String[] whereArgs) {
this.where = where;
this.whereArgs = whereArgs;
}

View File

@ -55,6 +55,7 @@ public class ContactConflictMergerTest {
.setUsername("username A")
.setProfileSharingEnabled(false)
.setArchived(false)
.setForcedUnread(false)
.build();
SignalContactRecord local = new SignalContactRecord.Builder(byteArray(2), new SignalServiceAddress(UUID_B, E164_B))
.setBlocked(false)
@ -66,6 +67,7 @@ public class ContactConflictMergerTest {
.setUsername("username B")
.setProfileSharingEnabled(true)
.setArchived(true)
.setForcedUnread(true)
.build();
SignalContactRecord merged = new ContactConflictMerger(Collections.singletonList(local), SELF).merge(remote, local, mock(KeyGenerator.class));
@ -81,6 +83,7 @@ public class ContactConflictMergerTest {
assertEquals("username A", merged.getUsername().get());
assertFalse(merged.isProfileSharingEnabled());
assertFalse(merged.isArchived());
assertFalse(merged.isForcedUnread());
}
@Test

View File

@ -30,11 +30,13 @@ public final class GroupV1ConflictMergerTest {
.setBlocked(false)
.setProfileSharingEnabled(false)
.setArchived(false)
.setForcedUnread(false)
.build();
SignalGroupV1Record local = new SignalGroupV1Record.Builder(byteArray(2), byteArray(100))
.setBlocked(true)
.setProfileSharingEnabled(true)
.setArchived(true)
.setForcedUnread(true)
.build();
SignalGroupV1Record merged = new GroupV1ConflictMerger(Collections.singletonList(local)).merge(remote, local, KEY_GENERATOR);
@ -44,6 +46,7 @@ public final class GroupV1ConflictMergerTest {
assertFalse(merged.isProfileSharingEnabled());
assertFalse(merged.isBlocked());
assertFalse(merged.isArchived());
assertFalse(merged.isForcedUnread());
}
@Test

View File

@ -30,11 +30,13 @@ public final class GroupV2ConflictMergerTest {
.setBlocked(false)
.setProfileSharingEnabled(false)
.setArchived(false)
.setForcedUnread(false)
.build();
SignalGroupV2Record local = new SignalGroupV2Record.Builder(byteArray(2), groupKey(100))
.setBlocked(true)
.setProfileSharingEnabled(true)
.setArchived(true)
.setForcedUnread(true)
.build();
SignalGroupV2Record merged = new GroupV2ConflictMerger(Collections.singletonList(local)).merge(remote, local, KEY_GENERATOR);
@ -44,6 +46,7 @@ public final class GroupV2ConflictMergerTest {
assertFalse(merged.isProfileSharingEnabled());
assertFalse(merged.isBlocked());
assertFalse(merged.isArchived());
assertFalse(merged.isForcedUnread());
}
@Test

View File

@ -8,6 +8,10 @@ import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.Arrays;
import edu.emory.mathcs.backport.java.util.Collections;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@ -23,7 +27,7 @@ public final class SqlUtilTest {
ContentValues values = new ContentValues();
values.put("a", 2);
SqlUtil.UpdateQuery updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, values);
SqlUtil.Query updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, values);
assertEquals("(_id = ?) AND (a != ? OR a IS NULL)", updateQuery.getWhere());
assertArrayEquals(new String[] { "1", "2" }, updateQuery.getWhereArgs());
@ -37,7 +41,7 @@ public final class SqlUtilTest {
ContentValues values = new ContentValues();
values.put("a", 4);
SqlUtil.UpdateQuery updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, values);
SqlUtil.Query updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, values);
assertEquals("(_id = ? AND (foo = ? OR bar != ?)) AND (a != ? OR a IS NULL)", updateQuery.getWhere());
assertArrayEquals(new String[] { "1", "2", "3", "4" }, updateQuery.getWhereArgs());
@ -53,7 +57,7 @@ public final class SqlUtilTest {
values.put("b", 3);
values.put("c", 4);
SqlUtil.UpdateQuery updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, values);
SqlUtil.Query updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, values);
assertEquals("(_id = ?) AND (a != ? OR a IS NULL OR b != ? OR b IS NULL OR c != ? OR c IS NULL)", updateQuery.getWhere());
assertArrayEquals(new String[] { "1", "2", "3", "4"}, updateQuery.getWhereArgs());
@ -67,7 +71,7 @@ public final class SqlUtilTest {
ContentValues values = new ContentValues();
values.put("a", (String) null);
SqlUtil.UpdateQuery updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, values);
SqlUtil.Query updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, values);
assertEquals("(_id = ?) AND (a NOT NULL)", updateQuery.getWhere());
assertArrayEquals(new String[] { "1" }, updateQuery.getWhereArgs());
@ -85,9 +89,30 @@ public final class SqlUtilTest {
values.put("d", (String) null);
values.put("e", (String) null);
SqlUtil.UpdateQuery updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, values);
SqlUtil.Query updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, values);
assertEquals("(_id = ?) AND (a NOT NULL OR b != ? OR b IS NULL OR c != ? OR c IS NULL OR d NOT NULL OR e NOT NULL)", updateQuery.getWhere());
assertArrayEquals(new String[] { "1", "2", "3" }, updateQuery.getWhereArgs());
}
@Test
public void buildCollectionQuery_single() {
SqlUtil.Query updateQuery = SqlUtil.buildCollectionQuery("a", Arrays.asList(1));
assertEquals("a IN (?)", updateQuery.getWhere());
assertArrayEquals(new String[] { "1" }, updateQuery.getWhereArgs());
}
@Test
public void buildCollectionQuery_multiple() {
SqlUtil.Query updateQuery = SqlUtil.buildCollectionQuery("a", Arrays.asList(1, 2, 3));
assertEquals("a IN (?, ?, ?)", updateQuery.getWhere());
assertArrayEquals(new String[] { "1", "2", "3" }, updateQuery.getWhereArgs());
}
@Test(expected = IllegalArgumentException.class)
public void buildCollectionQuery_none() {
SqlUtil.buildCollectionQuery("a", Collections.emptyList());
}
}

View File

@ -64,6 +64,10 @@ public final class SignalAccountRecord implements SignalRecord {
return proto.getNoteToSelfArchived();
}
public boolean isNoteToSelfForcedUnread() {
return proto.getNoteToSelfMarkedUnread();
}
public boolean isReadReceiptsEnabled() {
return proto.getReadReceipts();
}
@ -147,6 +151,11 @@ public final class SignalAccountRecord implements SignalRecord {
return this;
}
public Builder setNoteToSelfForcedUnread(boolean forcedUnread) {
builder.setNoteToSelfMarkedUnread(forcedUnread);
return this;
}
public Builder setReadReceiptsEnabled(boolean enabled) {
builder.setReadReceipts(enabled);
return this;

View File

@ -91,6 +91,10 @@ public final class SignalContactRecord implements SignalRecord {
return proto.getArchived();
}
public boolean isForcedUnread() {
return proto.getMarkedUnread();
}
ContactRecord toProto() {
return proto;
}
@ -173,6 +177,11 @@ public final class SignalContactRecord implements SignalRecord {
return this;
}
public Builder setForcedUnread(boolean forcedUnread) {
builder.setMarkedUnread(forcedUnread);
return this;
}
public SignalContactRecord build() {
ContactRecord proto = builder.build();

View File

@ -50,6 +50,10 @@ public final class SignalGroupV1Record implements SignalRecord {
return proto.getArchived();
}
public boolean isForcedUnread() {
return proto.getMarkedUnread();
}
GroupV1Record toProto() {
return proto;
}
@ -101,6 +105,11 @@ public final class SignalGroupV1Record implements SignalRecord {
return this;
}
public Builder setForcedUnread(boolean forcedUnread) {
builder.setMarkedUnread(forcedUnread);
return this;
}
public SignalGroupV1Record build() {
GroupV1Record proto = builder.build();

View File

@ -60,6 +60,10 @@ public final class SignalGroupV2Record implements SignalRecord {
return proto.getArchived();
}
public boolean isForcedUnread() {
return proto.getMarkedUnread();
}
GroupV2Record toProto() {
return proto;
}
@ -115,6 +119,11 @@ public final class SignalGroupV2Record implements SignalRecord {
return this;
}
public Builder setForcedUnread(boolean forcedUnread) {
builder.setMarkedUnread(forcedUnread);
return this;
}
public SignalGroupV2Record build() {
GroupV2Record proto = builder.build();

View File

@ -80,20 +80,23 @@ message ContactRecord {
bool blocked = 9;
bool whitelisted = 10;
bool archived = 11;
bool markedUnread = 12;
}
message GroupV1Record {
bytes id = 1;
bool blocked = 2;
bool whitelisted = 3;
bool archived = 4;
bytes id = 1;
bool blocked = 2;
bool whitelisted = 3;
bool archived = 4;
bool markedUnread = 5;
}
message GroupV2Record {
bytes masterKey = 1;
bool blocked = 2;
bool whitelisted = 3;
bool archived = 4;
bytes masterKey = 1;
bool blocked = 2;
bool whitelisted = 3;
bool archived = 4;
bool markedUnread = 5;
}
message AccountRecord {
@ -113,7 +116,7 @@ message AccountRecord {
bool sealedSenderIndicators = 7;
bool typingIndicators = 8;
bool proxiedLinkPreviews = 9;
// 10 is reserved for unread
bool noteToSelfMarkedUnread = 10;
bool linkPreviews = 11;
PhoneNumberSharingMode phoneNumberSharingMode = 12;
bool unlistedPhoneNumber = 13;