Update contact hiding to spec.

This commit is contained in:
Clark 2023-08-04 12:35:36 -04:00 committed by Alex Hart
parent c5d9346370
commit 6a87495a6d
14 changed files with 151 additions and 26 deletions

View file

@ -34,10 +34,10 @@ class ContactsManagementRepository(context: Context) {
}
val rotateProfileKey = !recipient.hasGroupsInCommon()
SignalDatabase.recipients.markHidden(recipient.id, rotateProfileKey)
SignalDatabase.recipients.markHidden(recipient.id, rotateProfileKey, false)
if (rotateProfileKey) {
ApplicationDependencies.getJobManager().add(RotateProfileKeyJob())
}
}
}.subscribeOn(Schedulers.io())
}
}

View file

@ -1113,7 +1113,7 @@ class ConversationFragment :
var inputDisabled = true
when {
inputReadyState.isClientExpired || inputReadyState.isUnauthorized -> disabledInputView.showAsExpiredOrUnauthorized(inputReadyState.isClientExpired, inputReadyState.isUnauthorized)
inputReadyState.messageRequestState != MessageRequestState.NONE -> disabledInputView.showAsMessageRequest(inputReadyState.conversationRecipient, inputReadyState.messageRequestState)
inputReadyState.messageRequestState != MessageRequestState.NONE && inputReadyState.messageRequestState != MessageRequestState.NONE_HIDDEN -> disabledInputView.showAsMessageRequest(inputReadyState.conversationRecipient, inputReadyState.messageRequestState)
inputReadyState.isActiveGroup == false -> disabledInputView.showAsNoLongerAMember()
inputReadyState.isRequestingMember == true -> disabledInputView.showAsRequestingMember()
inputReadyState.isAnnouncementGroup == true && inputReadyState.isAdmin == false -> disabledInputView.showAsAnnouncementGroupAdminsOnly()

View file

@ -491,6 +491,10 @@ class DistributionListTables constructor(context: Context?, databaseHelper: Sign
}
}
fun removeMemberFromAllLists(member: RecipientId) {
writableDatabase.delete(MembershipTable.TABLE_NAME, "${MembershipTable.RECIPIENT_ID} = ?", SqlUtil.buildArgs(member))
}
fun removeMemberFromList(listId: DistributionListId, privacyMode: DistributionListPrivacyMode, member: RecipientId) {
writableDatabase.delete(MembershipTable.TABLE_NAME, "${MembershipTable.LIST_ID} = ? AND ${MembershipTable.RECIPIENT_ID} = ? AND ${MembershipTable.PRIVACY_MODE} = ?", SqlUtil.buildArgs(listId, member, privacyMode.serialize()))
}

View file

@ -1708,6 +1708,65 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
}
}
/**
* Delete all the stories received from the recipient in 1:1 stories
*/
fun deleteStoriesForRecipient(recipientId: RecipientId): Int {
return writableDatabase.withinTransaction { db ->
val threadId = threads.getThreadIdFor(recipientId)
val storesInRecipientThread = "$IS_STORY_CLAUSE AND $THREAD_ID = ?"
val sharedArgs = buildArgs(threadId)
val deleteStoryRepliesQuery = """
DELETE FROM $TABLE_NAME
WHERE
$PARENT_STORY_ID > 0 AND
$PARENT_STORY_ID IN (
SELECT $ID
FROM $TABLE_NAME
WHERE $storesInRecipientThread
)
"""
val disassociateQuoteQuery = """
UPDATE $TABLE_NAME
SET
$QUOTE_MISSING = 1,
$QUOTE_BODY = ''
WHERE
$PARENT_STORY_ID < 0 AND
ABS($PARENT_STORY_ID) IN (
SELECT $ID
FROM $TABLE_NAME
WHERE $storesInRecipientThread
)
"""
db.execSQL(deleteStoryRepliesQuery, sharedArgs)
db.execSQL(disassociateQuoteQuery, sharedArgs)
ApplicationDependencies.getDatabaseObserver().notifyStoryObservers(recipientId)
val deletedStoryCount = db.select(ID)
.from(TABLE_NAME)
.where(storesInRecipientThread, sharedArgs)
.run()
.use { cursor ->
while (cursor.moveToNext()) {
deleteMessage(cursor.requireLong(ID))
}
cursor.count
}
if (deletedStoryCount > 0) {
OptimizeMessageSearchIndexJob.enqueue()
}
deletedStoryCount
}
}
private fun disassociateStoryQuotes(storyId: Long) {
writableDatabase
.update(TABLE_NAME)

View file

@ -1740,22 +1740,24 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
}
}
fun markHidden(id: RecipientId, clearProfileKey: Boolean = false) {
fun markHidden(id: RecipientId, clearProfileKey: Boolean = false, showMessageRequest: Boolean = false) {
val contentValues = if (clearProfileKey) {
contentValuesOf(
HIDDEN to 1,
HIDDEN to if (showMessageRequest) Recipient.HiddenState.HIDDEN_MESSAGE_REQUEST.serialize() else Recipient.HiddenState.HIDDEN.serialize(),
PROFILE_SHARING to 0,
PROFILE_KEY to null
)
} else {
contentValuesOf(
HIDDEN to 1,
HIDDEN to if (showMessageRequest) Recipient.HiddenState.HIDDEN_MESSAGE_REQUEST.serialize() else Recipient.HiddenState.HIDDEN.serialize(),
PROFILE_SHARING to 0
)
}
val updated = writableDatabase.update(TABLE_NAME, contentValues, "$ID_WHERE AND $TYPE = ?", SqlUtil.buildArgs(id, RecipientType.INDIVIDUAL.id)) > 0
if (updated) {
SignalDatabase.distributionLists.removeMemberFromAllLists(id)
SignalDatabase.messages.deleteStoriesForRecipient(id)
rotateStorageId(id)
ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(id)
StorageSyncHelper.scheduleSyncForDataChange()
@ -3033,7 +3035,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
fun getRegistered(): List<RecipientId> {
val results: MutableList<RecipientId> = LinkedList()
readableDatabase.query(TABLE_NAME, ID_PROJECTION, "$REGISTERED = ? and $HIDDEN = ?", arrayOf("1", "0"), null, null, null).use { cursor ->
readableDatabase.query(TABLE_NAME, ID_PROJECTION, "$REGISTERED = ? and $HIDDEN = ?", arrayOf("1", "${Recipient.HiddenState.NOT_HIDDEN.serialize()}"), null, null, null).use { cursor ->
while (cursor != null && cursor.moveToNext()) {
results.add(RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(ID))))
}
@ -3045,7 +3047,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
return readableDatabase
.select(ID)
.from(TABLE_NAME)
.where("$REGISTERED = ? and $HIDDEN = ? AND $ACI_COLUMN NOT NULL", 1, 0)
.where("$REGISTERED = ? and $HIDDEN = ? AND $ACI_COLUMN NOT NULL", 1, Recipient.HiddenState.NOT_HIDDEN.serialize())
.run()
.readToSet { cursor ->
RecipientId.from(cursor.requireLong(ID))
@ -3078,7 +3080,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
return readableDatabase
.select(E164)
.from(TABLE_NAME)
.where("$REGISTERED = ? and $HIDDEN = ? AND $E164 NOT NULL", 1, 0)
.where("$REGISTERED = ? and $HIDDEN = ? AND $E164 NOT NULL", 1, Recipient.HiddenState.NOT_HIDDEN.serialize())
.run()
.readToSet { cursor ->
cursor.requireNonNullString(E164)
@ -4194,7 +4196,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
hasGroupsInCommon = cursor.requireBoolean(GROUPS_IN_COMMON),
badges = parseBadgeList(cursor.requireBlob(BADGES)),
needsPniSignature = cursor.requireBoolean(NEEDS_PNI_SIGNATURE),
isHidden = cursor.requireBoolean(HIDDEN),
hiddenState = Recipient.HiddenState.deserialize(cursor.requireInt(HIDDEN)),
callLinkRoomId = cursor.requireString(CALL_LINK_ROOM_ID)?.let { CallLinkRoomId.DatabaseSerializer.deserialize(it) }
)
}

View file

@ -75,7 +75,7 @@ data class RecipientRecord(
val badges: List<Badge>,
@get:JvmName("needsPniSignature")
val needsPniSignature: Boolean,
val isHidden: Boolean,
val hiddenState: Recipient.HiddenState,
val callLinkRoomId: CallLinkRoomId?
) {

View file

@ -145,10 +145,15 @@ public final class MessageRequestRepository {
} else {
if (RecipientUtil.isMessageRequestAccepted(context, threadId)) {
return MessageRequestState.NONE;
} else if (RecipientUtil.isRecipientHidden(threadId)) {
return MessageRequestState.INDIVIDUAL_HIDDEN;
} else {
return MessageRequestState.INDIVIDUAL;
Recipient.HiddenState hiddenState = RecipientUtil.getRecipientHiddenState(threadId);
if (hiddenState == Recipient.HiddenState.NOT_HIDDEN) {
return MessageRequestState.INDIVIDUAL;
} else if (hiddenState == Recipient.HiddenState.HIDDEN) {
return MessageRequestState.NONE_HIDDEN;
} else {
return MessageRequestState.INDIVIDUAL_HIDDEN;
}
}
}
}

View file

@ -7,6 +7,9 @@ public enum MessageRequestState {
/** No message request necessary */
NONE,
/** No message request necessary as the user was hidden after accepting*/
NONE_HIDDEN,
/** A user is blocked */
BLOCKED_INDIVIDUAL,

View file

@ -77,6 +77,7 @@ import org.thoughtcrime.securesms.mms.QuoteModel
import org.thoughtcrime.securesms.mms.StickerSlide
import org.thoughtcrime.securesms.notifications.v2.ConversationId
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.Recipient.HiddenState
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.recipients.RecipientUtil
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage
@ -169,6 +170,10 @@ object DataMessageProcessor {
handleProfileKey(envelope.timestamp, message.profileKey.toByteArray(), senderRecipient)
}
if (groupId == null && senderRecipient.hiddenState == HiddenState.HIDDEN) {
SignalDatabase.recipients.markHidden(senderRecipient.id, clearProfileKey = false, showMessageRequest = true)
}
if (metadata.sealedSender && messageId != null) {
SignalExecutors.BOUNDED.execute { ApplicationDependencies.getJobManager().add(SendDeliveryReceiptJob(senderRecipient.id, message.timestamp, messageId)) }
} else if (!metadata.sealedSender) {

View file

@ -6,6 +6,7 @@ import android.net.Uri;
import android.text.TextUtils;
import androidx.annotation.AnyThread;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
@ -57,6 +58,8 @@ import org.whispersystems.signalservice.api.util.OptionalUtil;
import org.whispersystems.signalservice.api.util.Preconditions;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -116,7 +119,7 @@ public class Recipient {
private final String profileAvatar;
private final ProfileAvatarFileDetails profileAvatarFileDetails;
private final boolean profileSharing;
private final boolean isHidden;
private final Recipient.HiddenState hiddenState;
private final long lastProfileFetch;
private final String notificationChannel;
private final UnidentifiedAccessMode unidentifiedAccessMode;
@ -401,7 +404,7 @@ public class Recipient {
this.profileAvatar = null;
this.profileAvatarFileDetails = ProfileAvatarFileDetails.NO_DETAILS;
this.profileSharing = false;
this.isHidden = false;
this.hiddenState = HiddenState.NOT_HIDDEN;
this.lastProfileFetch = 0;
this.notificationChannel = null;
this.unidentifiedAccessMode = UnidentifiedAccessMode.DISABLED;
@ -456,7 +459,7 @@ public class Recipient {
this.profileAvatar = details.profileAvatar;
this.profileAvatarFileDetails = details.profileAvatarFileDetails;
this.profileSharing = details.profileSharing;
this.isHidden = details.isHidden;
this.hiddenState = details.hiddenState;
this.lastProfileFetch = details.lastProfileFetch;
this.notificationChannel = details.notificationChannel;
this.unidentifiedAccessMode = details.unidentifiedAccessMode;
@ -852,7 +855,11 @@ public class Recipient {
}
public boolean isHidden() {
return isHidden;
return hiddenState != HiddenState.NOT_HIDDEN;
}
public Recipient.HiddenState getHiddenState() {
return hiddenState;
}
public long getLastProfileFetchTime() {
@ -1244,6 +1251,31 @@ public class Recipient {
return Objects.hash(id);
}
public enum HiddenState {
NOT_HIDDEN(0),
HIDDEN(1),
HIDDEN_MESSAGE_REQUEST(2);
private final int value;
HiddenState(int value) {
this.value = value;
}
public int serialize() {
return value;
}
public static HiddenState deserialize(int value) {
switch (value) {
case 0: return NOT_HIDDEN;
case 1: return HIDDEN;
case 2: return HIDDEN_MESSAGE_REQUEST;
default: throw new IllegalArgumentException();
}
}
}
public enum Capability {
UNKNOWN(0),
SUPPORTED(1),
@ -1327,7 +1359,7 @@ public class Recipient {
expireMessages == other.expireMessages &&
Objects.equals(profileAvatarFileDetails, other.profileAvatarFileDetails) &&
profileSharing == other.profileSharing &&
isHidden == other.isHidden &&
hiddenState == other.hiddenState &&
Objects.equals(aci, other.aci) &&
Objects.equals(username, other.username) &&
Objects.equals(e164, other.e164) &&

View file

@ -10,7 +10,6 @@ import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import org.thoughtcrime.securesms.badges.models.Badge;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.conversation.colors.ChatColors;
import org.thoughtcrime.securesms.database.RecipientTable.InsightsBannerTier;
import org.thoughtcrime.securesms.database.RecipientTable.MentionSetting;
import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState;
import org.thoughtcrime.securesms.database.RecipientTable.UnidentifiedAccessMode;
@ -64,7 +63,7 @@ public class RecipientDetails {
final String profileAvatar;
final ProfileAvatarFileDetails profileAvatarFileDetails;
final boolean profileSharing;
final boolean isHidden;
final Recipient.HiddenState hiddenState;
final boolean isActiveGroup;
final long lastProfileFetch;
final boolean systemContact;
@ -128,7 +127,7 @@ public class RecipientDetails {
this.profileAvatar = record.getProfileAvatar();
this.profileAvatarFileDetails = record.getProfileAvatarFileDetails();
this.profileSharing = record.isProfileSharing();
this.isHidden = record.isHidden();
this.hiddenState = record.getHiddenState();
this.lastProfileFetch = record.getLastProfileFetch();
this.systemContact = systemContact;
this.isSelf = isSelf;
@ -181,7 +180,7 @@ public class RecipientDetails {
this.profileAvatar = null;
this.profileAvatarFileDetails = ProfileAvatarFileDetails.NO_DETAILS;
this.profileSharing = false;
this.isHidden = false;
this.hiddenState = Recipient.HiddenState.NOT_HIDDEN;
this.lastProfileFetch = 0;
this.systemContact = true;
this.isSelf = false;

View file

@ -198,6 +198,22 @@ public class RecipientUtil {
StorageSyncHelper.scheduleSyncForDataChange();
}
@WorkerThread
public static Recipient.HiddenState getRecipientHiddenState(long threadId) {
if (threadId < 0) {
return Recipient.HiddenState.NOT_HIDDEN;
}
ThreadTable threadTable = SignalDatabase.threads();
Recipient threadRecipient = threadTable.getRecipientForThreadId(threadId);
if (threadRecipient == null) {
return Recipient.HiddenState.NOT_HIDDEN;
}
return threadRecipient.getHiddenState();
}
@WorkerThread
public static boolean isRecipientHidden(long threadId) {
if (threadId < 0) {
@ -287,7 +303,7 @@ public class RecipientUtil {
boolean firstMessage = SignalDatabase.messages().getOutgoingSecureMessageCount(threadId) == 0;
if (firstMessage) {
if (firstMessage || recipient.isHidden()) {
SignalDatabase.recipients().setProfileSharing(recipient.getId(), true);
}
}

View file

@ -126,7 +126,7 @@ public final class StorageSyncModels {
.setMuteUntil(recipient.getMuteUntil())
.setHideStory(hideStory)
.setUnregisteredTimestamp(recipient.getSyncExtras().getUnregisteredTimestamp())
.setHidden(recipient.isHidden())
.setHidden(recipient.getHiddenState() != Recipient.HiddenState.NOT_HIDDEN)
.setUsername(recipient.getUsername())
.build();
}

View file

@ -150,7 +150,7 @@ object RecipientDatabaseTestUtils {
hasGroupsInCommon,
badges,
needsPniSignature = false,
isHidden = false,
hiddenState = Recipient.HiddenState.NOT_HIDDEN,
null
),
participants,