Update contact hiding to spec.
This commit is contained in:
parent
c5d9346370
commit
6a87495a6d
14 changed files with 151 additions and 26 deletions
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) }
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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?
|
||||
) {
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) &&
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -150,7 +150,7 @@ object RecipientDatabaseTestUtils {
|
|||
hasGroupsInCommon,
|
||||
badges,
|
||||
needsPniSignature = false,
|
||||
isHidden = false,
|
||||
hiddenState = Recipient.HiddenState.NOT_HIDDEN,
|
||||
null
|
||||
),
|
||||
participants,
|
||||
|
|
Loading…
Add table
Reference in a new issue