Refactor RecipientTable with a PNI constraint.

This commit is contained in:
Greyson Parrelli 2023-08-02 15:13:35 -04:00
parent 67b8f468e4
commit 5f289fa400
19 changed files with 411 additions and 223 deletions

View file

@ -1066,7 +1066,7 @@ class RecipientTableTest_getAndPossiblyMerge {
RecipientTable.TABLE_NAME, RecipientTable.TABLE_NAME,
null, null,
contentValuesOf( contentValuesOf(
RecipientTable.PHONE to e164, RecipientTable.E164 to e164,
RecipientTable.ACI_COLUMN to aci?.toString(), RecipientTable.ACI_COLUMN to aci?.toString(),
RecipientTable.PNI_COLUMN to pni?.toString(), RecipientTable.PNI_COLUMN to pni?.toString(),
RecipientTable.REGISTERED to RecipientTable.RegisteredState.REGISTERED.id RecipientTable.REGISTERED to RecipientTable.RegisteredState.REGISTERED.id

View file

@ -69,10 +69,10 @@ class InternalSearchViewModel : ViewModel() {
private fun RecipientRecord.displayName(): String { private fun RecipientRecord.displayName(): String {
return when { return when {
this.groupType == RecipientTable.GroupType.SIGNAL_V1 -> "GV1::${this.groupId}" this.recipientType == RecipientTable.RecipientType.GV1 -> "GV1::${this.groupId}"
this.groupType == RecipientTable.GroupType.SIGNAL_V2 -> "GV2::${this.groupId}" this.recipientType == RecipientTable.RecipientType.GV2 -> "GV2::${this.groupId}"
this.groupType == RecipientTable.GroupType.MMS -> "MMS_GROUP::${this.groupId}" this.recipientType == RecipientTable.RecipientType.MMS -> "MMS_GROUP::${this.groupId}"
this.groupType == RecipientTable.GroupType.DISTRIBUTION_LIST -> "DLIST::${this.distributionListId}" this.recipientType == RecipientTable.RecipientType.DISTRIBUTION_LIST -> "DLIST::${this.distributionListId}"
this.systemDisplayName?.isNotBlank() == true -> this.systemDisplayName this.systemDisplayName?.isNotBlank() == true -> this.systemDisplayName
this.signalProfileName.toString().isNotBlank() -> this.signalProfileName.serialize() this.signalProfileName.toString().isNotBlank() -> this.signalProfileName.serialize()
this.e164 != null -> this.e164 this.e164 != null -> this.e164

View file

@ -63,7 +63,7 @@ public class ContactRepository {
})); }));
add(new Pair<>(NUMBER_COLUMN, cursor -> { add(new Pair<>(NUMBER_COLUMN, cursor -> {
String phone = CursorUtil.requireString(cursor, RecipientTable.PHONE); String phone = CursorUtil.requireString(cursor, RecipientTable.E164);
String email = CursorUtil.requireString(cursor, RecipientTable.EMAIL); String email = CursorUtil.requireString(cursor, RecipientTable.EMAIL);
if (phone != null) { if (phone != null) {

View file

@ -941,7 +941,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
( (
sort_name GLOB ? OR sort_name GLOB ? OR
${RecipientTable.TABLE_NAME}.${RecipientTable.USERNAME} GLOB ? OR ${RecipientTable.TABLE_NAME}.${RecipientTable.USERNAME} GLOB ? OR
${RecipientTable.TABLE_NAME}.${RecipientTable.PHONE} GLOB ? OR ${RecipientTable.TABLE_NAME}.${RecipientTable.E164} GLOB ? OR
${RecipientTable.TABLE_NAME}.${RecipientTable.EMAIL} GLOB ? ${RecipientTable.TABLE_NAME}.${RecipientTable.EMAIL} GLOB ?
) )
""" """

View file

@ -53,7 +53,7 @@ class DistributionListTables constructor(context: Context?, databaseHelper: Sign
RecipientTable.TABLE_NAME, RecipientTable.TABLE_NAME,
null, null,
contentValuesOf( contentValuesOf(
RecipientTable.GROUP_TYPE to RecipientTable.GroupType.DISTRIBUTION_LIST.id, RecipientTable.TYPE to RecipientTable.RecipientType.DISTRIBUTION_LIST.id,
RecipientTable.DISTRIBUTION_LIST_ID to DistributionListId.MY_STORY_ID, RecipientTable.DISTRIBUTION_LIST_ID to DistributionListId.MY_STORY_ID,
RecipientTable.STORAGE_SERVICE_ID to Base64.encodeBytes(StorageSyncHelper.generateKey()), RecipientTable.STORAGE_SERVICE_ID to Base64.encodeBytes(StorageSyncHelper.generateKey()),
RecipientTable.PROFILE_SHARING to 1 RecipientTable.PROFILE_SHARING to 1

View file

@ -4421,7 +4421,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
FROM ${RecipientTable.TABLE_NAME} FROM ${RecipientTable.TABLE_NAME}
WHERE WHERE
${RecipientTable.TABLE_NAME}.${RecipientTable.ID} = $TO_RECIPIENT_ID AND ${RecipientTable.TABLE_NAME}.${RecipientTable.ID} = $TO_RECIPIENT_ID AND
${RecipientTable.TABLE_NAME}.${RecipientTable.GROUP_TYPE} != ${RecipientTable.GroupType.NONE.id} ${RecipientTable.TABLE_NAME}.${RecipientTable.TYPE} != ${RecipientTable.RecipientType.INDIVIDUAL.id}
) )
) )
$qualifierWhere $qualifierWhere

View file

@ -134,113 +134,113 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
const val TABLE_NAME = "recipient" const val TABLE_NAME = "recipient"
const val ID = "_id" const val ID = "_id"
const val TYPE = "type"
const val E164 = "e164"
const val ACI_COLUMN = "aci" const val ACI_COLUMN = "aci"
const val PNI_COLUMN = "pni" const val PNI_COLUMN = "pni"
const val USERNAME = "username" const val USERNAME = "username"
const val PHONE = "phone"
const val EMAIL = "email" const val EMAIL = "email"
const val GROUP_ID = "group_id" const val GROUP_ID = "group_id"
const val DISTRIBUTION_LIST_ID = "distribution_list_id" const val DISTRIBUTION_LIST_ID = "distribution_list_id"
private const val CALL_LINK_ROOM_ID = "call_link_room_id" const val CALL_LINK_ROOM_ID = "call_link_room_id"
const val GROUP_TYPE = "group_type"
const val BLOCKED = "blocked"
private const val MESSAGE_RINGTONE = "message_ringtone"
private const val MESSAGE_VIBRATE = "message_vibrate"
private const val CALL_RINGTONE = "call_ringtone"
private const val CALL_VIBRATE = "call_vibrate"
private const val NOTIFICATION_CHANNEL = "notification_channel"
private const val MUTE_UNTIL = "mute_until"
private const val AVATAR_COLOR = "color"
private const val SEEN_INVITE_REMINDER = "seen_invite_reminder"
private const val DEFAULT_SUBSCRIPTION_ID = "default_subscription_id"
private const val MESSAGE_EXPIRATION_TIME = "message_expiration_time"
const val REGISTERED = "registered" const val REGISTERED = "registered"
const val SYSTEM_JOINED_NAME = "system_display_name" const val UNREGISTERED_TIMESTAMP = "unregistered_timestamp"
const val SYSTEM_FAMILY_NAME = "system_family_name" const val BLOCKED = "blocked"
const val SYSTEM_GIVEN_NAME = "system_given_name" const val HIDDEN = "hidden"
const val SYSTEM_NICKNAME = "system_nickname" const val PROFILE_KEY = "profile_key"
private const val SYSTEM_PHOTO_URI = "system_photo_uri"
const val SYSTEM_PHONE_TYPE = "system_phone_type"
const val SYSTEM_PHONE_LABEL = "system_phone_label"
private const val SYSTEM_CONTACT_URI = "system_contact_uri"
private const val SYSTEM_INFO_PENDING = "system_info_pending"
private const val PROFILE_KEY = "profile_key"
const val EXPIRING_PROFILE_KEY_CREDENTIAL = "profile_key_credential" const val EXPIRING_PROFILE_KEY_CREDENTIAL = "profile_key_credential"
private const val SIGNAL_PROFILE_AVATAR = "signal_profile_avatar"
const val PROFILE_SHARING = "profile_sharing" const val PROFILE_SHARING = "profile_sharing"
private const val LAST_PROFILE_FETCH = "last_profile_fetch" const val PROFILE_GIVEN_NAME = "profile_given_name"
private const val UNIDENTIFIED_ACCESS_MODE = "unidentified_access_mode" const val PROFILE_FAMILY_NAME = "profile_family_name"
const val FORCE_SMS_SELECTION = "force_sms_selection"
private const val CAPABILITIES = "capabilities"
const val STORAGE_SERVICE_ID = "storage_service_key"
const val PROFILE_GIVEN_NAME = "signal_profile_name"
private const val PROFILE_FAMILY_NAME = "profile_family_name"
const val PROFILE_JOINED_NAME = "profile_joined_name" const val PROFILE_JOINED_NAME = "profile_joined_name"
private const val MENTION_SETTING = "mention_setting" const val PROFILE_AVATAR = "profile_avatar"
private const val STORAGE_PROTO = "storage_proto" const val LAST_PROFILE_FETCH = "last_profile_fetch"
private const val LAST_SESSION_RESET = "last_session_reset" const val SYSTEM_GIVEN_NAME = "system_given_name"
private const val WALLPAPER = "wallpaper" const val SYSTEM_FAMILY_NAME = "system_family_name"
private const val WALLPAPER_URI = "wallpaper_file" const val SYSTEM_JOINED_NAME = "system_joined_name"
const val SYSTEM_NICKNAME = "system_nickname"
const val SYSTEM_PHOTO_URI = "system_photo_uri"
const val SYSTEM_PHONE_LABEL = "system_phone_label"
const val SYSTEM_PHONE_TYPE = "system_phone_type"
const val SYSTEM_CONTACT_URI = "system_contact_uri"
const val SYSTEM_INFO_PENDING = "system_info_pending"
const val NOTIFICATION_CHANNEL = "notification_channel"
const val MESSAGE_RINGTONE = "message_ringtone"
const val MESSAGE_VIBRATE = "message_vibrate"
const val CALL_RINGTONE = "call_ringtone"
const val CALL_VIBRATE = "call_vibrate"
const val MUTE_UNTIL = "mute_until"
const val MESSAGE_EXPIRATION_TIME = "message_expiration_time"
const val SEALED_SENDER_MODE = "sealed_sender_mode"
const val STORAGE_SERVICE_ID = "storage_service_id"
const val STORAGE_SERVICE_PROTO = "storage_service_proto"
const val MENTION_SETTING = "mention_setting"
const val CAPABILITIES = "capabilities"
const val LAST_SESSION_RESET = "last_session_reset"
const val WALLPAPER = "wallpaper"
const val WALLPAPER_URI = "wallpaper_uri"
const val ABOUT = "about" const val ABOUT = "about"
const val ABOUT_EMOJI = "about_emoji" const val ABOUT_EMOJI = "about_emoji"
private const val EXTRAS = "extras" const val EXTRAS = "extras"
private const val GROUPS_IN_COMMON = "groups_in_common" const val GROUPS_IN_COMMON = "groups_in_common"
private const val CHAT_COLORS = "chat_colors" const val AVATAR_COLOR = "avatar_color"
private const val CUSTOM_CHAT_COLORS_ID = "custom_chat_colors_id" const val CHAT_COLORS = "chat_colors"
private const val BADGES = "badges" const val CUSTOM_CHAT_COLORS_ID = "custom_chat_colors_id"
const val BADGES = "badges"
const val NEEDS_PNI_SIGNATURE = "needs_pni_signature"
const val REPORTING_TOKEN = "reporting_token"
const val SEARCH_PROFILE_NAME = "search_signal_profile" const val SEARCH_PROFILE_NAME = "search_signal_profile"
const val SORT_NAME = "sort_name" const val SORT_NAME = "sort_name"
private const val IDENTITY_STATUS = "identity_status" const val IDENTITY_STATUS = "identity_status"
private const val IDENTITY_KEY = "identity_key" const val IDENTITY_KEY = "identity_key"
private const val NEEDS_PNI_SIGNATURE = "needs_pni_signature"
private const val UNREGISTERED_TIMESTAMP = "unregistered_timestamp"
const val HIDDEN = "hidden"
const val REPORTING_TOKEN = "reporting_token"
@JvmField @JvmField
val CREATE_TABLE = val CREATE_TABLE =
""" """
CREATE TABLE $TABLE_NAME ( CREATE TABLE $TABLE_NAME (
$ID INTEGER PRIMARY KEY AUTOINCREMENT, $ID INTEGER PRIMARY KEY AUTOINCREMENT,
$TYPE INTEGER DEFAULT ${RecipientType.INDIVIDUAL.id},
$E164 TEXT UNIQUE DEFAULT NULL,
$ACI_COLUMN TEXT UNIQUE DEFAULT NULL, $ACI_COLUMN TEXT UNIQUE DEFAULT NULL,
$PNI_COLUMN TEXT UNIQUE DEFAULT NULL CHECK (pni LIKE 'PNI:%'),
$USERNAME TEXT UNIQUE DEFAULT NULL, $USERNAME TEXT UNIQUE DEFAULT NULL,
$PHONE TEXT UNIQUE DEFAULT NULL,
$EMAIL TEXT UNIQUE DEFAULT NULL, $EMAIL TEXT UNIQUE DEFAULT NULL,
$GROUP_ID TEXT UNIQUE DEFAULT NULL, $GROUP_ID TEXT UNIQUE DEFAULT NULL,
$GROUP_TYPE INTEGER DEFAULT ${GroupType.NONE.id}, $DISTRIBUTION_LIST_ID INTEGER DEFAULT NULL,
$BLOCKED INTEGER DEFAULT 0, $CALL_LINK_ROOM_ID TEXT DEFAULT NULL,
$MESSAGE_RINGTONE TEXT DEFAULT NULL,
$MESSAGE_VIBRATE INTEGER DEFAULT ${VibrateState.DEFAULT.id},
$CALL_RINGTONE TEXT DEFAULT NULL,
$CALL_VIBRATE INTEGER DEFAULT ${VibrateState.DEFAULT.id},
$NOTIFICATION_CHANNEL TEXT DEFAULT NULL,
$MUTE_UNTIL INTEGER DEFAULT 0,
$AVATAR_COLOR TEXT DEFAULT NULL,
$SEEN_INVITE_REMINDER INTEGER DEFAULT ${InsightsBannerTier.NO_TIER.id},
$DEFAULT_SUBSCRIPTION_ID INTEGER DEFAULT -1,
$MESSAGE_EXPIRATION_TIME INTEGER DEFAULT 0,
$REGISTERED INTEGER DEFAULT ${RegisteredState.UNKNOWN.id}, $REGISTERED INTEGER DEFAULT ${RegisteredState.UNKNOWN.id},
$UNREGISTERED_TIMESTAMP INTEGER DEFAULT 0,
$BLOCKED INTEGER DEFAULT 0,
$HIDDEN INTEGER DEFAULT 0,
$PROFILE_KEY TEXT DEFAULT NULL,
$EXPIRING_PROFILE_KEY_CREDENTIAL TEXT DEFAULT NULL,
$PROFILE_SHARING INTEGER DEFAULT 0,
$PROFILE_GIVEN_NAME TEXT DEFAULT NULL,
$PROFILE_FAMILY_NAME TEXT DEFAULT NULL,
$PROFILE_JOINED_NAME TEXT DEFAULT NULL,
$PROFILE_AVATAR TEXT DEFAULT NULL,
$LAST_PROFILE_FETCH INTEGER DEFAULT 0,
$SYSTEM_GIVEN_NAME TEXT DEFAULT NULL, $SYSTEM_GIVEN_NAME TEXT DEFAULT NULL,
$SYSTEM_FAMILY_NAME TEXT DEFAULT NULL, $SYSTEM_FAMILY_NAME TEXT DEFAULT NULL,
$SYSTEM_JOINED_NAME TEXT DEFAULT NULL, $SYSTEM_JOINED_NAME TEXT DEFAULT NULL,
$SYSTEM_NICKNAME TEXT DEFAULT NULL,
$SYSTEM_PHOTO_URI TEXT DEFAULT NULL, $SYSTEM_PHOTO_URI TEXT DEFAULT NULL,
$SYSTEM_PHONE_LABEL TEXT DEFAULT NULL, $SYSTEM_PHONE_LABEL TEXT DEFAULT NULL,
$SYSTEM_PHONE_TYPE INTEGER DEFAULT -1, $SYSTEM_PHONE_TYPE INTEGER DEFAULT -1,
$SYSTEM_CONTACT_URI TEXT DEFAULT NULL, $SYSTEM_CONTACT_URI TEXT DEFAULT NULL,
$SYSTEM_INFO_PENDING INTEGER DEFAULT 0, $SYSTEM_INFO_PENDING INTEGER DEFAULT 0,
$PROFILE_KEY TEXT DEFAULT NULL, $NOTIFICATION_CHANNEL TEXT DEFAULT NULL,
$EXPIRING_PROFILE_KEY_CREDENTIAL TEXT DEFAULT NULL, $MESSAGE_RINGTONE TEXT DEFAULT NULL,
$PROFILE_GIVEN_NAME TEXT DEFAULT NULL, $MESSAGE_VIBRATE INTEGER DEFAULT ${VibrateState.DEFAULT.id},
$PROFILE_FAMILY_NAME TEXT DEFAULT NULL, $CALL_RINGTONE TEXT DEFAULT NULL,
$PROFILE_JOINED_NAME TEXT DEFAULT NULL, $CALL_VIBRATE INTEGER DEFAULT ${VibrateState.DEFAULT.id},
$SIGNAL_PROFILE_AVATAR TEXT DEFAULT NULL, $MUTE_UNTIL INTEGER DEFAULT 0,
$PROFILE_SHARING INTEGER DEFAULT 0, $MESSAGE_EXPIRATION_TIME INTEGER DEFAULT 0,
$LAST_PROFILE_FETCH INTEGER DEFAULT 0, $SEALED_SENDER_MODE INTEGER DEFAULT 0,
$UNIDENTIFIED_ACCESS_MODE INTEGER DEFAULT 0,
$FORCE_SMS_SELECTION INTEGER DEFAULT 0,
$STORAGE_SERVICE_ID TEXT UNIQUE DEFAULT NULL, $STORAGE_SERVICE_ID TEXT UNIQUE DEFAULT NULL,
$STORAGE_SERVICE_PROTO TEXT DEFAULT NULL,
$MENTION_SETTING INTEGER DEFAULT ${MentionSetting.ALWAYS_NOTIFY.id}, $MENTION_SETTING INTEGER DEFAULT ${MentionSetting.ALWAYS_NOTIFY.id},
$STORAGE_PROTO TEXT DEFAULT NULL,
$CAPABILITIES INTEGER DEFAULT 0, $CAPABILITIES INTEGER DEFAULT 0,
$LAST_SESSION_RESET BLOB DEFAULT NULL, $LAST_SESSION_RESET BLOB DEFAULT NULL,
$WALLPAPER BLOB DEFAULT NULL, $WALLPAPER BLOB DEFAULT NULL,
@ -249,78 +249,71 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
$ABOUT_EMOJI TEXT DEFAULT NULL, $ABOUT_EMOJI TEXT DEFAULT NULL,
$EXTRAS BLOB DEFAULT NULL, $EXTRAS BLOB DEFAULT NULL,
$GROUPS_IN_COMMON INTEGER DEFAULT 0, $GROUPS_IN_COMMON INTEGER DEFAULT 0,
$AVATAR_COLOR TEXT DEFAULT NULL,
$CHAT_COLORS BLOB DEFAULT NULL, $CHAT_COLORS BLOB DEFAULT NULL,
$CUSTOM_CHAT_COLORS_ID INTEGER DEFAULT 0, $CUSTOM_CHAT_COLORS_ID INTEGER DEFAULT 0,
$BADGES BLOB DEFAULT NULL, $BADGES BLOB DEFAULT NULL,
$PNI_COLUMN TEXT DEFAULT NULL,
$DISTRIBUTION_LIST_ID INTEGER DEFAULT NULL,
$NEEDS_PNI_SIGNATURE INTEGER DEFAULT 0, $NEEDS_PNI_SIGNATURE INTEGER DEFAULT 0,
$UNREGISTERED_TIMESTAMP INTEGER DEFAULT 0, $REPORTING_TOKEN BLOB DEFAULT NULL
$HIDDEN INTEGER DEFAULT 0,
$REPORTING_TOKEN BLOB DEFAULT NULL,
$SYSTEM_NICKNAME TEXT DEFAULT NULL,
$CALL_LINK_ROOM_ID TEXT DEFAULT NULL
) )
""" """
val CREATE_INDEXS = arrayOf( val CREATE_INDEXS = arrayOf(
"CREATE INDEX IF NOT EXISTS recipient_group_type_index ON $TABLE_NAME ($GROUP_TYPE);", "CREATE INDEX IF NOT EXISTS recipient_type_index ON $TABLE_NAME ($TYPE);",
"CREATE UNIQUE INDEX IF NOT EXISTS recipient_pni_index ON $TABLE_NAME ($PNI_COLUMN)", "CREATE INDEX IF NOT EXISTS recipient_aci_profile_key_index ON $TABLE_NAME ($ACI_COLUMN, $PROFILE_KEY) WHERE $ACI_COLUMN NOT NULL AND $PROFILE_KEY NOT NULL"
"CREATE INDEX IF NOT EXISTS recipient_service_id_profile_key ON $TABLE_NAME ($ACI_COLUMN, $PROFILE_KEY) WHERE $ACI_COLUMN NOT NULL AND $PROFILE_KEY NOT NULL"
) )
private val RECIPIENT_PROJECTION: Array<String> = arrayOf( private val RECIPIENT_PROJECTION: Array<String> = arrayOf(
ID, ID,
TYPE,
E164,
ACI_COLUMN, ACI_COLUMN,
PNI_COLUMN, PNI_COLUMN,
USERNAME, USERNAME,
PHONE,
EMAIL, EMAIL,
GROUP_ID, GROUP_ID,
GROUP_TYPE, DISTRIBUTION_LIST_ID,
BLOCKED, CALL_LINK_ROOM_ID,
MESSAGE_RINGTONE,
CALL_RINGTONE,
MESSAGE_VIBRATE,
CALL_VIBRATE,
MUTE_UNTIL,
AVATAR_COLOR,
MESSAGE_EXPIRATION_TIME,
REGISTERED, REGISTERED,
BLOCKED,
HIDDEN,
PROFILE_KEY, PROFILE_KEY,
EXPIRING_PROFILE_KEY_CREDENTIAL, EXPIRING_PROFILE_KEY_CREDENTIAL,
SYSTEM_JOINED_NAME, PROFILE_SHARING,
PROFILE_GIVEN_NAME,
PROFILE_FAMILY_NAME,
PROFILE_AVATAR,
LAST_PROFILE_FETCH,
SYSTEM_GIVEN_NAME, SYSTEM_GIVEN_NAME,
SYSTEM_FAMILY_NAME, SYSTEM_FAMILY_NAME,
SYSTEM_JOINED_NAME,
SYSTEM_PHOTO_URI, SYSTEM_PHOTO_URI,
SYSTEM_PHONE_LABEL, SYSTEM_PHONE_LABEL,
SYSTEM_PHONE_TYPE, SYSTEM_PHONE_TYPE,
SYSTEM_CONTACT_URI, SYSTEM_CONTACT_URI,
PROFILE_GIVEN_NAME,
PROFILE_FAMILY_NAME,
SIGNAL_PROFILE_AVATAR,
PROFILE_SHARING,
LAST_PROFILE_FETCH,
NOTIFICATION_CHANNEL, NOTIFICATION_CHANNEL,
UNIDENTIFIED_ACCESS_MODE, MESSAGE_RINGTONE,
CAPABILITIES, MESSAGE_VIBRATE,
CALL_RINGTONE,
CALL_VIBRATE,
MUTE_UNTIL,
MESSAGE_EXPIRATION_TIME,
SEALED_SENDER_MODE,
STORAGE_SERVICE_ID, STORAGE_SERVICE_ID,
MENTION_SETTING, MENTION_SETTING,
CAPABILITIES,
WALLPAPER, WALLPAPER,
WALLPAPER_URI, WALLPAPER_URI,
MENTION_SETTING,
ABOUT, ABOUT,
ABOUT_EMOJI, ABOUT_EMOJI,
EXTRAS, EXTRAS,
GROUPS_IN_COMMON, GROUPS_IN_COMMON,
AVATAR_COLOR,
CHAT_COLORS, CHAT_COLORS,
CUSTOM_CHAT_COLORS_ID, CUSTOM_CHAT_COLORS_ID,
BADGES, BADGES,
DISTRIBUTION_LIST_ID,
NEEDS_PNI_SIGNATURE, NEEDS_PNI_SIGNATURE,
HIDDEN, REPORTING_TOKEN
REPORTING_TOKEN,
CALL_LINK_ROOM_ID
) )
private val ID_PROJECTION = arrayOf(ID) private val ID_PROJECTION = arrayOf(ID)
@ -328,7 +321,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
private val SEARCH_PROJECTION = arrayOf( private val SEARCH_PROJECTION = arrayOf(
ID, ID,
SYSTEM_JOINED_NAME, SYSTEM_JOINED_NAME,
PHONE, E164,
EMAIL, EMAIL,
SYSTEM_PHONE_LABEL, SYSTEM_PHONE_LABEL,
SYSTEM_PHONE_TYPE, SYSTEM_PHONE_TYPE,
@ -355,7 +348,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
val SEARCH_PROJECTION_NAMES = arrayOf( val SEARCH_PROJECTION_NAMES = arrayOf(
ID, ID,
SYSTEM_JOINED_NAME, SYSTEM_JOINED_NAME,
PHONE, E164,
EMAIL, EMAIL,
SYSTEM_PHONE_LABEL, SYSTEM_PHONE_LABEL,
SYSTEM_PHONE_TYPE, SYSTEM_PHONE_TYPE,
@ -385,7 +378,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
NULLIF($PROFILE_JOINED_NAME, ''), NULLIF($PROFILE_JOINED_NAME, ''),
NULLIF($PROFILE_GIVEN_NAME, ''), NULLIF($PROFILE_GIVEN_NAME, ''),
NULLIF($USERNAME, ''), NULLIF($USERNAME, ''),
NULLIF($PHONE, '') NULLIF($E164, '')
), ),
' ', ' ',
'' ''
@ -398,7 +391,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
} }
fun getByE164(e164: String): Optional<RecipientId> { fun getByE164(e164: String): Optional<RecipientId> {
return getByColumn(PHONE, e164) return getByColumn(E164, e164)
} }
fun getByGroupId(groupId: GroupId): Optional<RecipientId> { fun getByGroupId(groupId: GroupId): Optional<RecipientId> {
@ -538,7 +531,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
DISTRIBUTION_LIST_ID, DISTRIBUTION_LIST_ID,
distributionListId.serialize(), distributionListId.serialize(),
ContentValues().apply { ContentValues().apply {
put(GROUP_TYPE, GroupType.DISTRIBUTION_LIST.id) put(TYPE, RecipientType.DISTRIBUTION_LIST.id)
put(DISTRIBUTION_LIST_ID, distributionListId.serialize()) put(DISTRIBUTION_LIST_ID, distributionListId.serialize())
put(STORAGE_SERVICE_ID, Base64.encodeBytes(storageId ?: StorageSyncHelper.generateKey())) put(STORAGE_SERVICE_ID, Base64.encodeBytes(storageId ?: StorageSyncHelper.generateKey()))
put(PROFILE_SHARING, 1) put(PROFILE_SHARING, 1)
@ -551,7 +544,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
CALL_LINK_ROOM_ID, CALL_LINK_ROOM_ID,
callLinkRoomId.serialize(), callLinkRoomId.serialize(),
contentValuesOf( contentValuesOf(
GROUP_TYPE to GroupType.CALL_LINK.id, TYPE to RecipientType.CALL_LINK.id,
CALL_LINK_ROOM_ID to callLinkRoomId.serialize(), CALL_LINK_ROOM_ID to callLinkRoomId.serialize(),
PROFILE_SHARING to 1 PROFILE_SHARING to 1
) )
@ -599,12 +592,12 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
} else { } else {
val groupUpdates = ContentValues().apply { val groupUpdates = ContentValues().apply {
if (groupId.isMms) { if (groupId.isMms) {
put(GROUP_TYPE, GroupType.MMS.id) put(TYPE, RecipientType.MMS.id)
} else { } else {
if (groupId.isV2) { if (groupId.isV2) {
put(GROUP_TYPE, GroupType.SIGNAL_V2.id) put(TYPE, RecipientType.GV2.id)
} else { } else {
put(GROUP_TYPE, GroupType.SIGNAL_V1.id) put(TYPE, RecipientType.GV1.id)
} }
put(STORAGE_SERVICE_ID, Base64.encodeBytes(StorageSyncHelper.generateKey())) put(STORAGE_SERVICE_ID, Base64.encodeBytes(StorageSyncHelper.generateKey()))
} }
@ -984,9 +977,9 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
put(STORAGE_SERVICE_ID, Base64.encodeBytes(update.new.id.raw)) put(STORAGE_SERVICE_ID, Base64.encodeBytes(update.new.id.raw))
if (update.new.hasUnknownFields()) { if (update.new.hasUnknownFields()) {
put(STORAGE_PROTO, Base64.encodeBytes(Objects.requireNonNull(update.new.serializeUnknownFields()))) put(STORAGE_SERVICE_PROTO, Base64.encodeBytes(Objects.requireNonNull(update.new.serializeUnknownFields())))
} else { } else {
putNull(STORAGE_PROTO) putNull(STORAGE_SERVICE_PROTO)
} }
} }
@ -1052,8 +1045,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
writableDatabase.withinTransaction { writableDatabase.withinTransaction {
for ((originalE164, updatedE164) in mapping) { for ((originalE164, updatedE164) in mapping) {
writableDatabase.update(TABLE_NAME) writableDatabase.update(TABLE_NAME)
.values(PHONE to updatedE164) .values(E164 to updatedE164)
.where("$PHONE = ?", originalE164) .where("$E164 = ?", originalE164)
.run(SQLiteDatabase.CONFLICT_IGNORE) .run(SQLiteDatabase.CONFLICT_IGNORE)
} }
} }
@ -1092,7 +1085,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
val out: MutableList<RecipientRecord> = ArrayList() val out: MutableList<RecipientRecord> = ArrayList()
val columns: Array<String> = TYPED_RECIPIENT_PROJECTION + arrayOf( val columns: Array<String> = TYPED_RECIPIENT_PROJECTION + arrayOf(
SYSTEM_NICKNAME, SYSTEM_NICKNAME,
"$TABLE_NAME.$STORAGE_PROTO", "$TABLE_NAME.$STORAGE_SERVICE_PROTO",
"$TABLE_NAME.$UNREGISTERED_TIMESTAMP", "$TABLE_NAME.$UNREGISTERED_TIMESTAMP",
"${GroupTable.TABLE_NAME}.${GroupTable.V2_MASTER_KEY}", "${GroupTable.TABLE_NAME}.${GroupTable.V2_MASTER_KEY}",
"${ThreadTable.TABLE_NAME}.${ThreadTable.ARCHIVED}", "${ThreadTable.TABLE_NAME}.${ThreadTable.ARCHIVED}",
@ -1124,14 +1117,14 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
val out: MutableMap<RecipientId, StorageId> = HashMap() val out: MutableMap<RecipientId, StorageId> = HashMap()
readableDatabase readableDatabase
.select(ID, STORAGE_SERVICE_ID, GROUP_TYPE) .select(ID, STORAGE_SERVICE_ID, TYPE)
.from(TABLE_NAME) .from(TABLE_NAME)
.where( .where(
""" """
$STORAGE_SERVICE_ID NOT NULL AND ( $STORAGE_SERVICE_ID NOT NULL AND (
($GROUP_TYPE = ? AND $ACI_COLUMN NOT NULL AND $ID != ?) ($TYPE = ? AND $ACI_COLUMN NOT NULL AND $ID != ?)
OR OR
$GROUP_TYPE = ? $TYPE = ?
OR OR
$DISTRIBUTION_LIST_ID NOT NULL AND $DISTRIBUTION_LIST_ID IN ( $DISTRIBUTION_LIST_ID NOT NULL AND $DISTRIBUTION_LIST_ID IN (
SELECT ${DistributionListTables.ListTable.ID} SELECT ${DistributionListTables.ListTable.ID}
@ -1139,22 +1132,22 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
) )
) )
""", """,
GroupType.NONE.id, RecipientType.INDIVIDUAL.id,
Recipient.self().id, Recipient.self().id,
GroupType.SIGNAL_V1.id RecipientType.GV1.id
) )
.run() .run()
.use { cursor -> .use { cursor ->
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
val id = RecipientId.from(cursor.requireLong(ID)) val id = RecipientId.from(cursor.requireLong(ID))
val encodedKey = cursor.requireNonNullString(STORAGE_SERVICE_ID) val encodedKey = cursor.requireNonNullString(STORAGE_SERVICE_ID)
val groupType = GroupType.fromId(cursor.requireInt(GROUP_TYPE)) val recipientType = RecipientType.fromId(cursor.requireInt(TYPE))
val key = Base64.decodeOrThrow(encodedKey) val key = Base64.decodeOrThrow(encodedKey)
when (groupType) { when (recipientType) {
GroupType.NONE -> out[id] = StorageId.forContact(key) RecipientType.INDIVIDUAL -> out[id] = StorageId.forContact(key)
GroupType.SIGNAL_V1 -> out[id] = StorageId.forGroupV1(key) RecipientType.GV1 -> out[id] = StorageId.forGroupV1(key)
GroupType.DISTRIBUTION_LIST -> out[id] = StorageId.forStoryDistributionList(key) RecipientType.DISTRIBUTION_LIST -> out[id] = StorageId.forStoryDistributionList(key)
else -> throw AssertionError() else -> throw AssertionError()
} }
} }
@ -1184,9 +1177,9 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
val out: MutableSet<String> = mutableSetOf() val out: MutableSet<String> = mutableSetOf()
for (query in queries) { for (query in queries) {
readableDatabase.query(TABLE_NAME, arrayOf(PHONE), query.where, query.whereArgs, null, null, null).use { cursor -> readableDatabase.query(TABLE_NAME, arrayOf(E164), query.where, query.whereArgs, null, null, null).use { cursor ->
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
val e164: String? = cursor.requireString(PHONE) val e164: String? = cursor.requireString(E164)
if (e164 != null) { if (e164 != null) {
out.add(e164) out.add(e164)
} }
@ -1427,7 +1420,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
fun setUnidentifiedAccessMode(id: RecipientId, unidentifiedAccessMode: UnidentifiedAccessMode) { fun setUnidentifiedAccessMode(id: RecipientId, unidentifiedAccessMode: UnidentifiedAccessMode) {
val values = ContentValues(1).apply { val values = ContentValues(1).apply {
put(UNIDENTIFIED_ACCESS_MODE, unidentifiedAccessMode.mode) put(SEALED_SENDER_MODE, unidentifiedAccessMode.mode)
} }
if (update(id, values)) { if (update(id, values)) {
ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(id) ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(id)
@ -1523,7 +1516,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
val valuesToSet = ContentValues(3).apply { val valuesToSet = ContentValues(3).apply {
put(PROFILE_KEY, encodedProfileKey) put(PROFILE_KEY, encodedProfileKey)
putNull(EXPIRING_PROFILE_KEY_CREDENTIAL) putNull(EXPIRING_PROFILE_KEY_CREDENTIAL)
put(UNIDENTIFIED_ACCESS_MODE, UnidentifiedAccessMode.UNKNOWN.mode) put(SEALED_SENDER_MODE, UnidentifiedAccessMode.UNKNOWN.mode)
} }
val updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, valuesToCompare) val updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, valuesToCompare)
@ -1555,7 +1548,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
val valuesToSet = ContentValues(3).apply { val valuesToSet = ContentValues(3).apply {
put(PROFILE_KEY, Base64.encodeBytes(profileKey.serialize())) put(PROFILE_KEY, Base64.encodeBytes(profileKey.serialize()))
putNull(EXPIRING_PROFILE_KEY_CREDENTIAL) putNull(EXPIRING_PROFILE_KEY_CREDENTIAL)
put(UNIDENTIFIED_ACCESS_MODE, UnidentifiedAccessMode.UNKNOWN.mode) put(SEALED_SENDER_MODE, UnidentifiedAccessMode.UNKNOWN.mode)
} }
if (writableDatabase.update(TABLE_NAME, valuesToSet, selection, args) > 0) { if (writableDatabase.update(TABLE_NAME, valuesToSet, selection, args) > 0) {
@ -1725,7 +1718,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
fun setProfileAvatar(id: RecipientId, profileAvatar: String?) { fun setProfileAvatar(id: RecipientId, profileAvatar: String?) {
val contentValues = ContentValues(1).apply { val contentValues = ContentValues(1).apply {
put(SIGNAL_PROFILE_AVATAR, profileAvatar) put(PROFILE_AVATAR, profileAvatar)
} }
if (update(id, contentValues)) { if (update(id, contentValues)) {
ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(id) ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(id)
@ -1761,7 +1754,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
) )
} }
val updated = writableDatabase.update(TABLE_NAME, contentValues, "$ID_WHERE AND $GROUP_TYPE = ?", SqlUtil.buildArgs(id, GroupType.NONE.id)) > 0 val updated = writableDatabase.update(TABLE_NAME, contentValues, "$ID_WHERE AND $TYPE = ?", SqlUtil.buildArgs(id, RecipientType.INDIVIDUAL.id)) > 0
if (updated) { if (updated) {
rotateStorageId(id) rotateStorageId(id)
ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(id) ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(id)
@ -1952,7 +1945,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
private fun removePhoneNumber(recipientId: RecipientId) { private fun removePhoneNumber(recipientId: RecipientId) {
val values = ContentValues().apply { val values = ContentValues().apply {
putNull(PHONE) putNull(E164)
putNull(PNI_COLUMN) putNull(PNI_COLUMN)
} }
@ -1967,7 +1960,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
@Throws(SQLiteConstraintException::class) @Throws(SQLiteConstraintException::class)
fun setPhoneNumberOrThrow(id: RecipientId, e164: String) { fun setPhoneNumberOrThrow(id: RecipientId, e164: String) {
val contentValues = ContentValues(1).apply { val contentValues = ContentValues(1).apply {
put(PHONE, e164) put(E164, e164)
} }
if (update(id, contentValues)) { if (update(id, contentValues)) {
rotateStorageId(id) rotateStorageId(id)
@ -1979,7 +1972,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
@Throws(SQLiteConstraintException::class) @Throws(SQLiteConstraintException::class)
fun setPhoneNumberOrThrowSilent(id: RecipientId, e164: String) { fun setPhoneNumberOrThrowSilent(id: RecipientId, e164: String) {
val contentValues = ContentValues(1).apply { val contentValues = ContentValues(1).apply {
put(PHONE, e164) put(E164, e164)
} }
if (update(id, contentValues)) { if (update(id, contentValues)) {
rotateStorageId(id) rotateStorageId(id)
@ -2071,9 +2064,9 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
fun getAllE164s(): Set<String> { fun getAllE164s(): Set<String> {
val results: MutableSet<String> = HashSet() val results: MutableSet<String> = HashSet()
readableDatabase.query(TABLE_NAME, arrayOf(PHONE), null, null, null, null, null).use { cursor -> readableDatabase.query(TABLE_NAME, arrayOf(E164), null, null, null, null, null).use { cursor ->
while (cursor != null && cursor.moveToNext()) { while (cursor != null && cursor.moveToNext()) {
val number = cursor.getString(cursor.getColumnIndexOrThrow(PHONE)) val number = cursor.getString(cursor.getColumnIndexOrThrow(E164))
if (!TextUtils.isEmpty(number)) { if (!TextUtils.isEmpty(number)) {
results.add(number) results.add(number)
} }
@ -2088,7 +2081,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
*/ */
fun getAllPossiblyRegisteredByE164(e164s: Set<String>): Set<RecipientId> { fun getAllPossiblyRegisteredByE164(e164s: Set<String>): Set<RecipientId> {
val results: MutableSet<RecipientId> = mutableSetOf() val results: MutableSet<RecipientId> = mutableSetOf()
val queries: List<SqlUtil.Query> = SqlUtil.buildCollectionQuery(PHONE, e164s) val queries: List<SqlUtil.Query> = SqlUtil.buildCollectionQuery(E164, e164s)
for (query in queries) { for (query in queries) {
readableDatabase.query(TABLE_NAME, arrayOf(ID, REGISTERED), query.where, query.whereArgs, null, null, null).use { cursor -> readableDatabase.query(TABLE_NAME, arrayOf(ID, REGISTERED), query.where, query.whereArgs, null, null, null).use { cursor ->
@ -2182,7 +2175,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
val contentValues = contentValuesOf( val contentValues = contentValuesOf(
REGISTERED to RegisteredState.NOT_REGISTERED.id, REGISTERED to RegisteredState.NOT_REGISTERED.id,
UNREGISTERED_TIMESTAMP to System.currentTimeMillis(), UNREGISTERED_TIMESTAMP to System.currentTimeMillis(),
PHONE to null, E164 to null,
PNI_COLUMN to null PNI_COLUMN to null
) )
@ -2226,7 +2219,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
.update(TABLE_NAME) .update(TABLE_NAME)
.values( .values(
PNI_COLUMN to null, PNI_COLUMN to null,
PHONE to null E164 to null
) )
.where("$ID = ?", record.id) .where("$ID = ?", record.id)
.run() .run()
@ -2423,7 +2416,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
is PnpOperation.RemoveE164 -> { is PnpOperation.RemoveE164 -> {
writableDatabase writableDatabase
.update(TABLE_NAME) .update(TABLE_NAME)
.values(PHONE to null) .values(E164 to null)
.where("$ID = ?", operation.recipientId) .where("$ID = ?", operation.recipientId)
.run() .run()
} }
@ -2448,7 +2441,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
is PnpOperation.SetE164 -> { is PnpOperation.SetE164 -> {
writableDatabase writableDatabase
.update(TABLE_NAME) .update(TABLE_NAME)
.values(PHONE to operation.e164) .values(E164 to operation.e164)
.where("$ID = ?", operation.recipientId) .where("$ID = ?", operation.recipientId)
.run() .run()
} }
@ -3083,12 +3076,12 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
fun getRegisteredE164s(): Set<String> { fun getRegisteredE164s(): Set<String> {
return readableDatabase return readableDatabase
.select(PHONE) .select(E164)
.from(TABLE_NAME) .from(TABLE_NAME)
.where("$REGISTERED = ? and $HIDDEN = ? AND $PHONE NOT NULL", 1, 0) .where("$REGISTERED = ? and $HIDDEN = ? AND $E164 NOT NULL", 1, 0)
.run() .run()
.readToSet { cursor -> .readToSet { cursor ->
cursor.requireNonNullString(PHONE) cursor.requireNonNullString(E164)
} }
} }
@ -3164,7 +3157,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
} }
fun getSignalContacts(includeSelf: Boolean): Cursor? { fun getSignalContacts(includeSelf: Boolean): Cursor? {
return getSignalContacts(includeSelf, "$SORT_NAME, $SYSTEM_JOINED_NAME, $SEARCH_PROFILE_NAME, $USERNAME, $PHONE") return getSignalContacts(includeSelf, "$SORT_NAME, $SYSTEM_JOINED_NAME, $SEARCH_PROFILE_NAME, $USERNAME, $E164")
} }
fun getSignalContactsCount(includeSelf: Boolean): Int { fun getSignalContactsCount(includeSelf: Boolean): Int {
@ -3193,7 +3186,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
.build() .build()
val selection = searchSelection.where val selection = searchSelection.where
val args = searchSelection.args val args = searchSelection.args
val orderBy = "$SORT_NAME, $SYSTEM_JOINED_NAME, $SEARCH_PROFILE_NAME, $PHONE" val orderBy = "$SORT_NAME, $SYSTEM_JOINED_NAME, $SEARCH_PROFILE_NAME, $E164"
return readableDatabase.query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy) return readableDatabase.query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy)
} }
@ -3216,7 +3209,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
SELECT ${SEARCH_PROJECTION.joinToString(", ")} SELECT ${SEARCH_PROJECTION.joinToString(", ")}
FROM recipient FROM recipient
WHERE ${searchSelection.where} WHERE ${searchSelection.where}
ORDER BY $SORT_NAME, $SYSTEM_JOINED_NAME, $SEARCH_PROFILE_NAME, $PHONE ORDER BY $SORT_NAME, $SYSTEM_JOINED_NAME, $SEARCH_PROFILE_NAME, $E164
) )
GROUP BY letter_header GROUP BY letter_header
""", """,
@ -3243,7 +3236,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
.build() .build()
val selection = searchSelection.where val selection = searchSelection.where
val args = searchSelection.args val args = searchSelection.args
val orderBy = "$SYSTEM_JOINED_NAME, $PHONE" val orderBy = "$SYSTEM_JOINED_NAME, $E164"
return readableDatabase.query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy) return readableDatabase.query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy)
} }
@ -3256,7 +3249,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
.build() .build()
val selection = searchSelection.where val selection = searchSelection.where
val args = searchSelection.args val args = searchSelection.args
val orderBy = "$SYSTEM_JOINED_NAME, $PHONE" val orderBy = "$SYSTEM_JOINED_NAME, $E164"
return readableDatabase.query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy) return readableDatabase.query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy)
} }
@ -3267,7 +3260,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
.withGroups(false) .withGroups(false)
.excludeId(if (includeSelf) null else Recipient.self().id) .excludeId(if (includeSelf) null else Recipient.self().id)
.build() .build()
val orderBy = orderByPreferringAlphaOverNumeric(SORT_NAME) + ", " + PHONE val orderBy = orderByPreferringAlphaOverNumeric(SORT_NAME) + ", " + E164
return readableDatabase.query(TABLE_NAME, SEARCH_PROJECTION, searchSelection.where, searchSelection.args, null, null, orderBy) return readableDatabase.query(TABLE_NAME, SEARCH_PROJECTION, searchSelection.where, searchSelection.args, null, null, orderBy)
} }
@ -3283,7 +3276,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
.build() .build()
val selection = searchSelection.where val selection = searchSelection.where
val args = searchSelection.args val args = searchSelection.args
val orderBy = orderByPreferringAlphaOverNumeric(SORT_NAME) + ", " + PHONE val orderBy = orderByPreferringAlphaOverNumeric(SORT_NAME) + ", " + E164
return readableDatabase.query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy) return readableDatabase.query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy)
} }
@ -3294,7 +3287,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
.excludeId(Recipient.self().id) .excludeId(Recipient.self().id)
.build() .build()
val orderBy = orderByPreferringAlphaOverNumeric(SORT_NAME) + ", " + PHONE val orderBy = orderByPreferringAlphaOverNumeric(SORT_NAME) + ", " + E164
return readableDatabase.query(TABLE_NAME, SEARCH_PROJECTION, searchSelection.where, searchSelection.args, null, null, orderBy) return readableDatabase.query(TABLE_NAME, SEARCH_PROJECTION, searchSelection.where, searchSelection.args, null, null, orderBy)
} }
@ -3308,7 +3301,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
val selection = searchSelection.where val selection = searchSelection.where
val args = searchSelection.args val args = searchSelection.args
val orderBy = orderByPreferringAlphaOverNumeric(SORT_NAME) + ", " + PHONE val orderBy = orderByPreferringAlphaOverNumeric(SORT_NAME) + ", " + E164
return readableDatabase.query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy) return readableDatabase.query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy)
} }
@ -3321,7 +3314,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
( (
$SORT_NAME GLOB ? OR $SORT_NAME GLOB ? OR
$USERNAME GLOB ? OR $USERNAME GLOB ? OR
$PHONE GLOB ? OR $E164 GLOB ? OR
$EMAIL GLOB ? $EMAIL GLOB ?
) )
""" """
@ -3342,7 +3335,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
( (
$SORT_NAME GLOB ? OR $SORT_NAME GLOB ? OR
$USERNAME GLOB ? OR $USERNAME GLOB ? OR
$PHONE GLOB ? OR $E164 GLOB ? OR
$EMAIL GLOB ? $EMAIL GLOB ?
)) ))
""" """
@ -3363,7 +3356,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
AND ( AND (
$SORT_NAME GLOB ? OR $SORT_NAME GLOB ? OR
$USERNAME GLOB ? OR $USERNAME GLOB ? OR
$PHONE GLOB ? OR $E164 GLOB ? OR
$EMAIL GLOB ? $EMAIL GLOB ?
) )
""" """
@ -3483,7 +3476,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
} }
for (e164 in blockedE164) { for (e164 in blockedE164) {
db.update(TABLE_NAME, setBlocked, "$PHONE = ?", arrayOf(e164)) db.update(TABLE_NAME, setBlocked, "$E164 = ?", arrayOf(e164))
} }
for (uuid in blockedUuid) { for (uuid in blockedUuid) {
@ -3671,8 +3664,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
put(STORAGE_SERVICE_ID, Base64.encodeBytes(StorageSyncHelper.generateKey())) put(STORAGE_SERVICE_ID, Base64.encodeBytes(StorageSyncHelper.generateKey()))
} }
val query = "$ID = ? AND ($GROUP_TYPE IN (?, ?, ?) OR $REGISTERED = ?)" val query = "$ID = ? AND ($TYPE IN (?, ?, ?) OR $REGISTERED = ?)"
val args = SqlUtil.buildArgs(recipientId, GroupType.SIGNAL_V1.id, GroupType.SIGNAL_V2.id, GroupType.DISTRIBUTION_LIST.id, RegisteredState.REGISTERED.id) val args = SqlUtil.buildArgs(recipientId, RecipientType.GV1.id, RecipientType.GV2.id, RecipientType.DISTRIBUTION_LIST.id, RegisteredState.REGISTERED.id)
writableDatabase.update(TABLE_NAME, values, query, args) writableDatabase.update(TABLE_NAME, values, query, args)
} }
@ -3696,7 +3689,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
fun updateGroupId(v1Id: V1, v2Id: V2) { fun updateGroupId(v1Id: V1, v2Id: V2) {
val values = ContentValues().apply { val values = ContentValues().apply {
put(GROUP_ID, v2Id.toString()) put(GROUP_ID, v2Id.toString())
put(GROUP_TYPE, GroupType.SIGNAL_V2.id) put(TYPE, RecipientType.GV2.id)
} }
val query = SqlUtil.buildTrueUpdateQuery("$GROUP_ID = ?", SqlUtil.buildArgs(v1Id), values) val query = SqlUtil.buildTrueUpdateQuery("$GROUP_ID = ?", SqlUtil.buildArgs(v1Id), values)
@ -3814,7 +3807,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
RemappedRecords.getInstance().addRecipient(secondaryId, primaryId) RemappedRecords.getInstance().addRecipient(secondaryId, primaryId)
val uuidValues = contentValuesOf( val uuidValues = contentValuesOf(
PHONE to (secondaryRecord.e164 ?: primaryRecord.e164), E164 to (secondaryRecord.e164 ?: primaryRecord.e164),
ACI_COLUMN to (primaryRecord.aci ?: secondaryRecord.aci)?.toString(), ACI_COLUMN to (primaryRecord.aci ?: secondaryRecord.aci)?.toString(),
PNI_COLUMN to (newPni ?: secondaryRecord.pni ?: primaryRecord.pni)?.toString(), PNI_COLUMN to (newPni ?: secondaryRecord.pni ?: primaryRecord.pni)?.toString(),
BLOCKED to (secondaryRecord.isBlocked || primaryRecord.isBlocked), BLOCKED to (secondaryRecord.isBlocked || primaryRecord.isBlocked),
@ -3866,7 +3859,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
check(e164 != null || pni != null || aci != null) { "Must provide some sort of identifier!" } check(e164 != null || pni != null || aci != null) { "Must provide some sort of identifier!" }
val values = contentValuesOf( val values = contentValuesOf(
PHONE to e164, E164 to e164,
ACI_COLUMN to aci?.toString(), ACI_COLUMN to aci?.toString(),
PNI_COLUMN to pni?.toString(), PNI_COLUMN to pni?.toString(),
STORAGE_SERVICE_ID to Base64.encodeBytes(StorageSyncHelper.generateKey()), STORAGE_SERVICE_ID to Base64.encodeBytes(StorageSyncHelper.generateKey()),
@ -3895,7 +3888,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
put(PNI_COLUMN, contact.pni.orElse(null)?.toString()) put(PNI_COLUMN, contact.pni.orElse(null)?.toString())
} }
put(PHONE, contact.number.orElse(null)) put(E164, contact.number.orElse(null))
put(PROFILE_GIVEN_NAME, profileName.givenName) put(PROFILE_GIVEN_NAME, profileName.givenName)
put(PROFILE_FAMILY_NAME, profileName.familyName) put(PROFILE_FAMILY_NAME, profileName.familyName)
put(PROFILE_JOINED_NAME, profileName.toString()) put(PROFILE_JOINED_NAME, profileName.toString())
@ -3912,9 +3905,9 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
put(HIDDEN, contact.isHidden) put(HIDDEN, contact.isHidden)
if (contact.hasUnknownFields()) { if (contact.hasUnknownFields()) {
put(STORAGE_PROTO, Base64.encodeBytes(Objects.requireNonNull(contact.serializeUnknownFields()))) put(STORAGE_SERVICE_PROTO, Base64.encodeBytes(Objects.requireNonNull(contact.serializeUnknownFields())))
} else { } else {
putNull(STORAGE_PROTO) putNull(STORAGE_SERVICE_PROTO)
} }
put(UNREGISTERED_TIMESTAMP, contact.unregisteredTimestamp) put(UNREGISTERED_TIMESTAMP, contact.unregisteredTimestamp)
@ -3937,16 +3930,16 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
val groupId = GroupId.v1orThrow(groupV1.groupId) val groupId = GroupId.v1orThrow(groupV1.groupId)
put(GROUP_ID, groupId.toString()) put(GROUP_ID, groupId.toString())
put(GROUP_TYPE, GroupType.SIGNAL_V1.id) put(TYPE, RecipientType.GV1.id)
put(PROFILE_SHARING, if (groupV1.isProfileSharingEnabled) "1" else "0") put(PROFILE_SHARING, if (groupV1.isProfileSharingEnabled) "1" else "0")
put(BLOCKED, if (groupV1.isBlocked) "1" else "0") put(BLOCKED, if (groupV1.isBlocked) "1" else "0")
put(MUTE_UNTIL, groupV1.muteUntil) put(MUTE_UNTIL, groupV1.muteUntil)
put(STORAGE_SERVICE_ID, Base64.encodeBytes(groupV1.id.raw)) put(STORAGE_SERVICE_ID, Base64.encodeBytes(groupV1.id.raw))
if (groupV1.hasUnknownFields()) { if (groupV1.hasUnknownFields()) {
put(STORAGE_PROTO, Base64.encodeBytes(groupV1.serializeUnknownFields())) put(STORAGE_SERVICE_PROTO, Base64.encodeBytes(groupV1.serializeUnknownFields()))
} else { } else {
putNull(STORAGE_PROTO) putNull(STORAGE_SERVICE_PROTO)
} }
if (isInsert) { if (isInsert) {
@ -3960,7 +3953,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
val groupId = GroupId.v2(groupV2.masterKeyOrThrow) val groupId = GroupId.v2(groupV2.masterKeyOrThrow)
put(GROUP_ID, groupId.toString()) put(GROUP_ID, groupId.toString())
put(GROUP_TYPE, GroupType.SIGNAL_V2.id) put(TYPE, RecipientType.GV2.id)
put(PROFILE_SHARING, if (groupV2.isProfileSharingEnabled) "1" else "0") put(PROFILE_SHARING, if (groupV2.isProfileSharingEnabled) "1" else "0")
put(BLOCKED, if (groupV2.isBlocked) "1" else "0") put(BLOCKED, if (groupV2.isBlocked) "1" else "0")
put(MUTE_UNTIL, groupV2.muteUntil) put(MUTE_UNTIL, groupV2.muteUntil)
@ -3968,9 +3961,9 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
put(MENTION_SETTING, if (groupV2.notifyForMentionsWhenMuted()) MentionSetting.ALWAYS_NOTIFY.id else MentionSetting.DO_NOT_NOTIFY.id) put(MENTION_SETTING, if (groupV2.notifyForMentionsWhenMuted()) MentionSetting.ALWAYS_NOTIFY.id else MentionSetting.DO_NOT_NOTIFY.id)
if (groupV2.hasUnknownFields()) { if (groupV2.hasUnknownFields()) {
put(STORAGE_PROTO, Base64.encodeBytes(groupV2.serializeUnknownFields())) put(STORAGE_SERVICE_PROTO, Base64.encodeBytes(groupV2.serializeUnknownFields()))
} else { } else {
putNull(STORAGE_PROTO) putNull(STORAGE_SERVICE_PROTO)
} }
if (isInsert) { if (isInsert) {
@ -4013,9 +4006,9 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
) )
.run { .run {
if (recipientId == null) { if (recipientId == null) {
where("$ID != ? AND $PHONE NOT NULL", Recipient.self().id) where("$ID != ? AND $E164 NOT NULL", Recipient.self().id)
} else { } else {
where("$ID = ? AND $PHONE NOT NULL", recipientId) where("$ID = ? AND $E164 NOT NULL", recipientId)
} }
} }
.run() .run()
@ -4039,7 +4032,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
PROFILE_FAMILY_NAME to null, PROFILE_FAMILY_NAME to null,
PROFILE_JOINED_NAME to null, PROFILE_JOINED_NAME to null,
LAST_PROFILE_FETCH to 0, LAST_PROFILE_FETCH to 0,
SIGNAL_PROFILE_AVATAR to null PROFILE_AVATAR to null
) )
.run { .run {
if (recipientId == null) { if (recipientId == null) {
@ -4063,7 +4056,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
writableDatabase writableDatabase
.update(TABLE_NAME) .update(TABLE_NAME)
.values( .values(
PHONE to null, E164 to null,
PNI_COLUMN to null PNI_COLUMN to null
) )
.where(ID_WHERE, recipientId) .where(ID_WHERE, recipientId)
@ -4161,11 +4154,11 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
aci = ACI.parseOrNull(cursor.requireString(ACI_COLUMN)), aci = ACI.parseOrNull(cursor.requireString(ACI_COLUMN)),
pni = PNI.parseOrNull(cursor.requireString(PNI_COLUMN)), pni = PNI.parseOrNull(cursor.requireString(PNI_COLUMN)),
username = cursor.requireString(USERNAME), username = cursor.requireString(USERNAME),
e164 = cursor.requireString(PHONE), e164 = cursor.requireString(E164),
email = cursor.requireString(EMAIL), email = cursor.requireString(EMAIL),
groupId = GroupId.parseNullableOrThrow(cursor.requireString(GROUP_ID)), groupId = GroupId.parseNullableOrThrow(cursor.requireString(GROUP_ID)),
distributionListId = distributionListId, distributionListId = distributionListId,
groupType = GroupType.fromId(cursor.requireInt(GROUP_TYPE)), recipientType = RecipientType.fromId(cursor.requireInt(TYPE)),
isBlocked = cursor.requireBoolean(BLOCKED), isBlocked = cursor.requireBoolean(BLOCKED),
muteUntil = cursor.requireLong(MUTE_UNTIL), muteUntil = cursor.requireLong(MUTE_UNTIL),
messageVibrateState = VibrateState.fromId(cursor.requireInt(MESSAGE_VIBRATE)), messageVibrateState = VibrateState.fromId(cursor.requireInt(MESSAGE_VIBRATE)),
@ -4182,12 +4175,12 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
systemPhoneLabel = cursor.requireString(SYSTEM_PHONE_LABEL), systemPhoneLabel = cursor.requireString(SYSTEM_PHONE_LABEL),
systemContactUri = cursor.requireString(SYSTEM_CONTACT_URI), systemContactUri = cursor.requireString(SYSTEM_CONTACT_URI),
signalProfileName = ProfileName.fromParts(cursor.requireString(PROFILE_GIVEN_NAME), cursor.requireString(PROFILE_FAMILY_NAME)), signalProfileName = ProfileName.fromParts(cursor.requireString(PROFILE_GIVEN_NAME), cursor.requireString(PROFILE_FAMILY_NAME)),
signalProfileAvatar = cursor.requireString(SIGNAL_PROFILE_AVATAR), signalProfileAvatar = cursor.requireString(PROFILE_AVATAR),
profileAvatarFileDetails = AvatarHelper.getAvatarFileDetails(context, recipientId), profileAvatarFileDetails = AvatarHelper.getAvatarFileDetails(context, recipientId),
profileSharing = cursor.requireBoolean(PROFILE_SHARING), profileSharing = cursor.requireBoolean(PROFILE_SHARING),
lastProfileFetch = cursor.requireLong(LAST_PROFILE_FETCH), lastProfileFetch = cursor.requireLong(LAST_PROFILE_FETCH),
notificationChannel = cursor.requireString(NOTIFICATION_CHANNEL), notificationChannel = cursor.requireString(NOTIFICATION_CHANNEL),
unidentifiedAccessMode = UnidentifiedAccessMode.fromMode(cursor.requireInt(UNIDENTIFIED_ACCESS_MODE)), unidentifiedAccessMode = UnidentifiedAccessMode.fromMode(cursor.requireInt(SEALED_SENDER_MODE)),
capabilities = readCapabilities(cursor), capabilities = readCapabilities(cursor),
storageId = Base64.decodeNullableOrThrow(cursor.requireString(STORAGE_SERVICE_ID)), storageId = Base64.decodeNullableOrThrow(cursor.requireString(STORAGE_SERVICE_ID)),
mentionSetting = MentionSetting.fromId(cursor.requireInt(MENTION_SETTING)), mentionSetting = MentionSetting.fromId(cursor.requireInt(MENTION_SETTING)),
@ -4246,7 +4239,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
} }
private fun getSyncExtras(cursor: Cursor): RecipientRecord.SyncExtras { private fun getSyncExtras(cursor: Cursor): RecipientRecord.SyncExtras {
val storageProtoRaw = cursor.optionalString(STORAGE_PROTO).orElse(null) val storageProtoRaw = cursor.optionalString(STORAGE_SERVICE_PROTO).orElse(null)
val storageProto = if (storageProtoRaw != null) Base64.decodeOrThrow(storageProtoRaw) else null val storageProto = if (storageProtoRaw != null) Base64.decodeOrThrow(storageProtoRaw) else null
val archived = cursor.optionalBoolean(ThreadTable.ARCHIVED).orElse(false) val archived = cursor.optionalBoolean(ThreadTable.ARCHIVED).orElse(false)
val forcedUnread = cursor.optionalInt(ThreadTable.READ).map { status: Int -> status == ThreadTable.ReadStatus.FORCED_UNREAD.serialize() }.orElse(false) val forcedUnread = cursor.optionalInt(ThreadTable.READ).map { status: Int -> status == ThreadTable.ReadStatus.FORCED_UNREAD.serialize() }.orElse(false)
@ -4287,7 +4280,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
values.apply { values.apply {
put(PROFILE_KEY, if (record.profileKey != null) Base64.encodeBytes(record.profileKey) else null) put(PROFILE_KEY, if (record.profileKey != null) Base64.encodeBytes(record.profileKey) else null)
putNull(EXPIRING_PROFILE_KEY_CREDENTIAL) putNull(EXPIRING_PROFILE_KEY_CREDENTIAL)
put(SIGNAL_PROFILE_AVATAR, record.signalProfileAvatar) put(PROFILE_AVATAR, record.signalProfileAvatar)
put(PROFILE_GIVEN_NAME, record.signalProfileName.givenName) put(PROFILE_GIVEN_NAME, record.signalProfileName.givenName)
put(PROFILE_FAMILY_NAME, record.signalProfileName.familyName) put(PROFILE_FAMILY_NAME, record.signalProfileName.familyName)
put(PROFILE_JOINED_NAME, record.signalProfileName.toString()) put(PROFILE_JOINED_NAME, record.signalProfileName.toString())
@ -4556,12 +4549,12 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
const val FILTER_ID = " AND $ID != ?" const val FILTER_ID = " AND $ID != ?"
const val FILTER_BLOCKED = " AND $BLOCKED = ?" const val FILTER_BLOCKED = " AND $BLOCKED = ?"
const val FILTER_HIDDEN = " AND $HIDDEN = ?" const val FILTER_HIDDEN = " AND $HIDDEN = ?"
const val NON_SIGNAL_CONTACT = "$REGISTERED != ? AND $SYSTEM_CONTACT_URI NOT NULL AND ($PHONE NOT NULL OR $EMAIL NOT NULL)" const val NON_SIGNAL_CONTACT = "$REGISTERED != ? AND $SYSTEM_CONTACT_URI NOT NULL AND ($E164 NOT NULL OR $EMAIL NOT NULL)"
const val QUERY_NON_SIGNAL_CONTACT = "$NON_SIGNAL_CONTACT AND ($PHONE GLOB ? OR $EMAIL GLOB ? OR $SYSTEM_JOINED_NAME GLOB ?)" const val QUERY_NON_SIGNAL_CONTACT = "$NON_SIGNAL_CONTACT AND ($E164 GLOB ? OR $EMAIL GLOB ? OR $SYSTEM_JOINED_NAME GLOB ?)"
const val SIGNAL_CONTACT = "$REGISTERED = ? AND (NULLIF($SYSTEM_JOINED_NAME, '') NOT NULL OR $PROFILE_SHARING = ?) AND ($SORT_NAME NOT NULL OR $USERNAME NOT NULL)" const val SIGNAL_CONTACT = "$REGISTERED = ? AND (NULLIF($SYSTEM_JOINED_NAME, '') NOT NULL OR $PROFILE_SHARING = ?) AND ($SORT_NAME NOT NULL OR $USERNAME NOT NULL)"
const val QUERY_SIGNAL_CONTACT = "$SIGNAL_CONTACT AND ($PHONE GLOB ? OR $SORT_NAME GLOB ? OR $USERNAME GLOB ?)" const val QUERY_SIGNAL_CONTACT = "$SIGNAL_CONTACT AND ($E164 GLOB ? OR $SORT_NAME GLOB ? OR $USERNAME GLOB ?)"
val GROUP_MEMBER_CONTACT = "$REGISTERED = ? AND $HAS_GROUP_IN_COMMON AND NOT (NULLIF($SYSTEM_JOINED_NAME, '') NOT NULL OR $PROFILE_SHARING = ?) AND ($SORT_NAME NOT NULL OR $USERNAME NOT NULL)" val GROUP_MEMBER_CONTACT = "$REGISTERED = ? AND $HAS_GROUP_IN_COMMON AND NOT (NULLIF($SYSTEM_JOINED_NAME, '') NOT NULL OR $PROFILE_SHARING = ?) AND ($SORT_NAME NOT NULL OR $USERNAME NOT NULL)"
val QUERY_GROUP_MEMBER_CONTACT = "$GROUP_MEMBER_CONTACT AND ($PHONE GLOB ? OR $SORT_NAME GLOB ? OR $USERNAME GLOB ?)" val QUERY_GROUP_MEMBER_CONTACT = "$GROUP_MEMBER_CONTACT AND ($E164 GLOB ? OR $SORT_NAME GLOB ? OR $USERNAME GLOB ?)"
} }
} }
@ -4631,11 +4624,11 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
} }
} }
enum class GroupType(val id: Int) { enum class RecipientType(val id: Int) {
NONE(0), MMS(1), SIGNAL_V1(2), SIGNAL_V2(3), DISTRIBUTION_LIST(4), CALL_LINK(5); INDIVIDUAL(0), MMS(1), GV1(2), GV2(3), DISTRIBUTION_LIST(4), CALL_LINK(5);
companion object { companion object {
fun fromId(id: Int): GroupType { fun fromId(id: Int): RecipientType {
return values()[id] return values()[id]
} }
} }

View file

@ -721,7 +721,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
} }
if (hideV1Groups) { if (hideV1Groups) {
where += " AND ${RecipientTable.TABLE_NAME}.${RecipientTable.GROUP_TYPE} != ${RecipientTable.GroupType.SIGNAL_V1.id}" where += " AND ${RecipientTable.TABLE_NAME}.${RecipientTable.TYPE} != ${RecipientTable.RecipientType.GV1.id}"
} }
if (hideSms) { if (hideSms) {
@ -730,10 +730,9 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
OR OR
( (
${RecipientTable.TABLE_NAME}.${RecipientTable.GROUP_ID} NOT NULL ${RecipientTable.TABLE_NAME}.${RecipientTable.GROUP_ID} NOT NULL
AND ${RecipientTable.TABLE_NAME}.${RecipientTable.GROUP_TYPE} != ${RecipientTable.GroupType.MMS.id} AND ${RecipientTable.TABLE_NAME}.${RecipientTable.TYPE} != ${RecipientTable.RecipientType.MMS.id}
) )
)""" )"""
where += " AND ${RecipientTable.TABLE_NAME}.${RecipientTable.FORCE_SMS_SELECTION} = 0"
} }
if (hideSelf) { if (hideSelf) {

View file

@ -56,6 +56,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V197_DropAvatarColo
import org.thoughtcrime.securesms.database.helpers.migration.V198_AddMacDigestColumn import org.thoughtcrime.securesms.database.helpers.migration.V198_AddMacDigestColumn
import org.thoughtcrime.securesms.database.helpers.migration.V199_AddThreadActiveColumn import org.thoughtcrime.securesms.database.helpers.migration.V199_AddThreadActiveColumn
import org.thoughtcrime.securesms.database.helpers.migration.V200_ResetPniColumn import org.thoughtcrime.securesms.database.helpers.migration.V200_ResetPniColumn
import org.thoughtcrime.securesms.database.helpers.migration.V201_RecipientTableValidations
/** /**
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness. * Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
@ -64,7 +65,7 @@ object SignalDatabaseMigrations {
val TAG: String = Log.tag(SignalDatabaseMigrations.javaClass) val TAG: String = Log.tag(SignalDatabaseMigrations.javaClass)
const val DATABASE_VERSION = 200 const val DATABASE_VERSION = 201
@JvmStatic @JvmStatic
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
@ -275,6 +276,10 @@ object SignalDatabaseMigrations {
if (oldVersion < 200) { if (oldVersion < 200) {
V200_ResetPniColumn.migrate(context, db, oldVersion, newVersion) V200_ResetPniColumn.migrate(context, db, oldVersion, newVersion)
} }
if (oldVersion < 201) {
V201_RecipientTableValidations.migrate(context, db, oldVersion, newVersion)
}
} }
@JvmStatic @JvmStatic

View file

@ -0,0 +1,151 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database.helpers.migration
import android.app.Application
import net.zetetic.database.sqlcipher.SQLiteDatabase
/**
* This migration rebuilds the recipient table to drop some deprecated columns, rename others to match their intended name, and some new constraints.
* Specifically, we add a CHECK constraint to the `pni` column to ensure that we only put serialized PNI's in there.
*/
@Suppress("ClassName")
object V201_RecipientTableValidations : SignalDatabaseMigration {
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL(
"""
CREATE TABLE recipient_tmp (
_id INTEGER PRIMARY KEY AUTOINCREMENT,
type INTEGER DEFAULT 0,
e164 TEXT UNIQUE DEFAULT NULL,
aci TEXT UNIQUE DEFAULT NULL,
pni TEXT UNIQUE DEFAULT NULL CHECK (pni LIKE 'PNI:%'),
username TEXT UNIQUE DEFAULT NULL,
email TEXT UNIQUE DEFAULT NULL,
group_id TEXT UNIQUE DEFAULT NULL,
distribution_list_id INTEGER DEFAULT NULL,
call_link_room_id TEXT DEFAULT NULL,
registered INTEGER DEFAULT 0,
unregistered_timestamp INTEGER DEFAULT 0,
blocked INTEGER DEFAULT 0,
hidden INTEGER DEFAULT 0,
profile_key TEXT DEFAULT NULL,
profile_key_credential TEXT DEFAULT NULL,
profile_sharing INTEGER DEFAULT 0,
profile_given_name TEXT DEFAULT NULL,
profile_family_name TEXT DEFAULT NULL,
profile_joined_name TEXT DEFAULT NULL,
profile_avatar TEXT DEFAULT NULL,
last_profile_fetch INTEGER DEFAULT 0,
system_given_name TEXT DEFAULT NULL,
system_family_name TEXT DEFAULT NULL,
system_joined_name TEXT DEFAULT NULL,
system_nickname TEXT DEFAULT NULL,
system_photo_uri TEXT DEFAULT NULL,
system_phone_label TEXT DEFAULT NULL,
system_phone_type INTEGER DEFAULT -1,
system_contact_uri TEXT DEFAULT NULL,
system_info_pending INTEGER DEFAULT 0,
notification_channel TEXT DEFAULT NULL,
message_ringtone TEXT DEFAULT NULL,
message_vibrate INTEGER DEFAULT 0,
call_ringtone TEXT DEFAULT NULL,
call_vibrate INTEGER DEFAULT 0,
mute_until INTEGER DEFAULT 0,
message_expiration_time INTEGER DEFAULT 0,
sealed_sender_mode INTEGER DEFAULT 0,
storage_service_id TEXT UNIQUE DEFAULT NULL,
storage_service_proto TEXT DEFAULT NULL,
mention_setting INTEGER DEFAULT 0,
capabilities INTEGER DEFAULT 0,
last_session_reset BLOB DEFAULT NULL,
wallpaper BLOB DEFAULT NULL,
wallpaper_uri TEXT DEFAULT NULL,
about TEXT DEFAULT NULL,
about_emoji TEXT DEFAULT NULL,
extras BLOB DEFAULT NULL,
groups_in_common INTEGER DEFAULT 0,
avatar_color TEXT DEFAULT NULL,
chat_colors BLOB DEFAULT NULL,
custom_chat_colors_id INTEGER DEFAULT 0,
badges BLOB DEFAULT NULL,
needs_pni_signature INTEGER DEFAULT 0,
reporting_token BLOB DEFAULT NULL
)
"""
)
db.execSQL(
"""
INSERT INTO recipient_tmp SELECT
_id,
group_type,
phone,
aci,
pni,
username,
email,
group_id,
distribution_list_id,
call_link_room_id,
registered,
unregistered_timestamp,
blocked,
hidden,
profile_key,
profile_key_credential,
profile_sharing,
signal_profile_name,
profile_family_name,
profile_joined_name,
signal_profile_avatar,
last_profile_fetch,
system_given_name,
system_family_name,
system_display_name,
system_nickname,
system_photo_uri,
system_phone_label,
system_phone_type,
system_contact_uri,
system_info_pending,
notification_channel,
message_ringtone,
message_vibrate,
call_ringtone,
call_vibrate,
mute_until,
message_expiration_time,
unidentified_access_mode,
storage_service_key,
storage_proto,
mention_setting,
capabilities,
last_session_reset,
wallpaper,
wallpaper_file,
about,
about_emoji,
extras,
groups_in_common,
color,
chat_colors,
custom_chat_colors_id,
badges,
needs_pni_signature,
reporting_token
FROM
recipient
"""
)
db.execSQL("DROP TABLE recipient")
db.execSQL("ALTER TABLE recipient_tmp RENAME TO recipient")
db.execSQL("CREATE INDEX recipient_type_index ON recipient (type)")
db.execSQL("CREATE INDEX recipient_aci_profile_key_index ON recipient (aci, profile_key) WHERE aci NOT NULL AND profile_key NOT NULL")
}
}

View file

@ -34,7 +34,7 @@ data class RecipientRecord(
val email: String?, val email: String?,
val groupId: GroupId?, val groupId: GroupId?,
val distributionListId: DistributionListId?, val distributionListId: DistributionListId?,
val groupType: RecipientTable.GroupType, val recipientType: RecipientTable.RecipientType,
val isBlocked: Boolean, val isBlocked: Boolean,
val muteUntil: Long, val muteUntil: Long,
val messageVibrateState: VibrateState, val messageVibrateState: VibrateState,

View file

@ -440,7 +440,7 @@ public class StorageSyncJob extends BaseJob {
case ManifestRecord.Identifier.Type.GROUPV2_VALUE: case ManifestRecord.Identifier.Type.GROUPV2_VALUE:
RecipientRecord settings = recipientTable.getByStorageId(id.getRaw()); RecipientRecord settings = recipientTable.getByStorageId(id.getRaw());
if (settings != null) { if (settings != null) {
if (settings.getGroupType() == RecipientTable.GroupType.SIGNAL_V2 && settings.getSyncExtras().getGroupMasterKey() == null) { if (settings.getRecipientType() == RecipientTable.RecipientType.GV2 && settings.getSyncExtras().getGroupMasterKey() == null) {
throw new MissingGv2MasterKeyError(); throw new MissingGv2MasterKeyError();
} else { } else {
records.add(StorageSyncModels.localToRemoteRecord(settings)); records.add(StorageSyncModels.localToRemoteRecord(settings));

View file

@ -18,7 +18,6 @@ import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.subscription.Subscriber; import org.thoughtcrime.securesms.subscription.Subscriber;
import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.storage.SignalAccountRecord; import org.whispersystems.signalservice.api.storage.SignalAccountRecord;
import org.whispersystems.signalservice.api.storage.SignalContactRecord; import org.whispersystems.signalservice.api.storage.SignalContactRecord;
@ -56,10 +55,10 @@ public final class StorageSyncModels {
} }
public static @NonNull SignalStorageRecord localToRemoteRecord(@NonNull RecipientRecord settings, @NonNull byte[] rawStorageId) { public static @NonNull SignalStorageRecord localToRemoteRecord(@NonNull RecipientRecord settings, @NonNull byte[] rawStorageId) {
switch (settings.getGroupType()) { switch (settings.getRecipientType()) {
case NONE: return SignalStorageRecord.forContact(localToRemoteContact(settings, rawStorageId)); case INDIVIDUAL: return SignalStorageRecord.forContact(localToRemoteContact(settings, rawStorageId));
case SIGNAL_V1: return SignalStorageRecord.forGroupV1(localToRemoteGroupV1(settings, rawStorageId)); case GV1: return SignalStorageRecord.forGroupV1(localToRemoteGroupV1(settings, rawStorageId));
case SIGNAL_V2: return SignalStorageRecord.forGroupV2(localToRemoteGroupV2(settings, rawStorageId, settings.getSyncExtras().getGroupMasterKey())); case GV2: return SignalStorageRecord.forGroupV2(localToRemoteGroupV2(settings, rawStorageId, settings.getSyncExtras().getGroupMasterKey()));
case DISTRIBUTION_LIST: return SignalStorageRecord.forStoryDistributionList(localToRemoteStoryDistributionList(settings, rawStorageId)); case DISTRIBUTION_LIST: return SignalStorageRecord.forStoryDistributionList(localToRemoteStoryDistributionList(settings, rawStorageId));
default: throw new AssertionError("Unsupported type!"); default: throw new AssertionError("Unsupported type!");
} }
@ -85,18 +84,18 @@ public final class StorageSyncModels {
public static List<SignalAccountRecord.PinnedConversation> localToRemotePinnedConversations(@NonNull List<RecipientRecord> settings) { public static List<SignalAccountRecord.PinnedConversation> localToRemotePinnedConversations(@NonNull List<RecipientRecord> settings) {
return Stream.of(settings) return Stream.of(settings)
.filter(s -> s.getGroupType() == RecipientTable.GroupType.SIGNAL_V1 || .filter(s -> s.getRecipientType() == RecipientTable.RecipientType.GV1 ||
s.getGroupType() == RecipientTable.GroupType.SIGNAL_V2 || s.getRecipientType() == RecipientTable.RecipientType.GV2 ||
s.getRegistered() == RecipientTable.RegisteredState.REGISTERED) s.getRegistered() == RecipientTable.RegisteredState.REGISTERED)
.map(StorageSyncModels::localToRemotePinnedConversation) .map(StorageSyncModels::localToRemotePinnedConversation)
.toList(); .toList();
} }
private static @NonNull SignalAccountRecord.PinnedConversation localToRemotePinnedConversation(@NonNull RecipientRecord settings) { private static @NonNull SignalAccountRecord.PinnedConversation localToRemotePinnedConversation(@NonNull RecipientRecord settings) {
switch (settings.getGroupType()) { switch (settings.getRecipientType()) {
case NONE : return SignalAccountRecord.PinnedConversation.forContact(new SignalServiceAddress(settings.getAci(), settings.getE164())); case INDIVIDUAL: return SignalAccountRecord.PinnedConversation.forContact(new SignalServiceAddress(settings.getAci(), settings.getE164()));
case SIGNAL_V1: return SignalAccountRecord.PinnedConversation.forGroupV1(settings.getGroupId().requireV1().getDecodedId()); case GV1: return SignalAccountRecord.PinnedConversation.forGroupV1(settings.getGroupId().requireV1().getDecodedId());
case SIGNAL_V2: return SignalAccountRecord.PinnedConversation.forGroupV2(settings.getSyncExtras().getGroupMasterKey().serialize()); case GV2: return SignalAccountRecord.PinnedConversation.forGroupV2(settings.getSyncExtras().getGroupMasterKey().serialize());
default : throw new AssertionError("Unexpected group type!"); default : throw new AssertionError("Unexpected group type!");
} }
} }

View file

@ -84,7 +84,7 @@ public class StoryDistributionListRecordProcessor extends DefaultStorageRecordPr
throw new IllegalStateException("Found matching recipient but couldn't generate record for sync."); throw new IllegalStateException("Found matching recipient but couldn't generate record for sync.");
} }
if (recordForSync.getGroupType().getId() != RecipientTable.GroupType.DISTRIBUTION_LIST.getId()) { if (recordForSync.getRecipientType().getId() != RecipientTable.RecipientType.DISTRIBUTION_LIST.getId()) {
Log.d(TAG, "Record has an incorrect group type."); Log.d(TAG, "Record has an incorrect group type.");
throw new InvalidGroupTypeException(); throw new InvalidGroupTypeException();
} }

View file

@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.database.MessageBitmaskColumnTransformer
import org.thoughtcrime.securesms.database.MessageRangesTransformer import org.thoughtcrime.securesms.database.MessageRangesTransformer
import org.thoughtcrime.securesms.database.ProfileKeyCredentialTransformer import org.thoughtcrime.securesms.database.ProfileKeyCredentialTransformer
import org.thoughtcrime.securesms.database.QueryMonitor import org.thoughtcrime.securesms.database.QueryMonitor
import org.thoughtcrime.securesms.database.RecipientTransformer
import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.TimestampTransformer import org.thoughtcrime.securesms.database.TimestampTransformer
import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.SignalStore
@ -52,7 +53,7 @@ class SpinnerApplicationContext : ApplicationContext() {
linkedMapOf( linkedMapOf(
"signal" to DatabaseConfig( "signal" to DatabaseConfig(
db = { SignalDatabase.rawDatabase }, db = { SignalDatabase.rawDatabase },
columnTransformers = listOf(MessageBitmaskColumnTransformer, GV2Transformer, GV2UpdateTransformer, IsStoryTransformer, TimestampTransformer, ProfileKeyCredentialTransformer, MessageRangesTransformer, KyberKeyTransformer) columnTransformers = listOf(MessageBitmaskColumnTransformer, GV2Transformer, GV2UpdateTransformer, IsStoryTransformer, TimestampTransformer, ProfileKeyCredentialTransformer, MessageRangesTransformer, KyberKeyTransformer, RecipientTransformer)
), ),
"jobmanager" to DatabaseConfig(db = { JobDatabase.getInstance(this).sqlCipherDatabase }), "jobmanager" to DatabaseConfig(db = { JobDatabase.getInstance(this).sqlCipherDatabase }),
"keyvalue" to DatabaseConfig(db = { KeyValueDatabase.getInstance(this).sqlCipherDatabase }), "keyvalue" to DatabaseConfig(db = { KeyValueDatabase.getInstance(this).sqlCipherDatabase }),

View file

@ -64,7 +64,11 @@ import org.thoughtcrime.securesms.database.MessageTypes.UNSUPPORTED_MESSAGE_TYPE
object MessageBitmaskColumnTransformer : ColumnTransformer { object MessageBitmaskColumnTransformer : ColumnTransformer {
override fun matches(tableName: String?, columnName: String): Boolean { override fun matches(tableName: String?, columnName: String): Boolean {
return columnName == "type" || columnName == "msg_box" return if (tableName != null && tableName != MessageTable.TABLE_NAME) {
false
} else {
columnName == "type" || columnName == "msg_box"
}
} }
@Suppress("FoldInitializerAndIfToElvis") @Suppress("FoldInitializerAndIfToElvis")

View file

@ -0,0 +1,15 @@
package org.thoughtcrime.securesms.database
import android.database.Cursor
import org.signal.core.util.requireInt
import org.signal.spinner.ColumnTransformer
object RecipientTransformer : ColumnTransformer {
override fun matches(tableName: String?, columnName: String): Boolean {
return tableName == RecipientTable.TABLE_NAME && columnName == RecipientTable.TYPE
}
override fun transform(tableName: String?, columnName: String, cursor: Cursor): String? {
return RecipientTable.RecipientType.fromId(cursor.requireInt(RecipientTable.TYPE)).toString()
}
}

View file

@ -39,7 +39,7 @@ object RecipientDatabaseTestUtils {
e164: String? = null, e164: String? = null,
email: String? = null, email: String? = null,
groupId: GroupId? = null, groupId: GroupId? = null,
groupType: RecipientTable.GroupType = RecipientTable.GroupType.NONE, groupType: RecipientTable.RecipientType = RecipientTable.RecipientType.INDIVIDUAL,
blocked: Boolean = false, blocked: Boolean = false,
muteUntil: Long = -1, muteUntil: Long = -1,
messageVibrateState: RecipientTable.VibrateState = RecipientTable.VibrateState.DEFAULT, messageVibrateState: RecipientTable.VibrateState = RecipientTable.VibrateState.DEFAULT,

View file

@ -2,6 +2,7 @@ package org.signal.core.util
import android.database.Cursor import android.database.Cursor
import androidx.core.database.getLongOrNull import androidx.core.database.getLongOrNull
import androidx.core.database.getStringOrNull
import java.util.Optional import java.util.Optional
fun Cursor.requireString(column: String): String? { fun Cursor.requireString(column: String): String? {
@ -181,3 +182,23 @@ inline fun Cursor.forEach(operation: (Cursor) -> Unit) {
} }
fun Boolean.toInt(): Int = if (this) 1 else 0 fun Boolean.toInt(): Int = if (this) 1 else 0
/**
* Renders the entire cursor row as a string.
* Not necessarily used in the app, but very useful to have available when debugging.
*/
fun Cursor.rowToString(): String {
val builder = StringBuilder()
for (i in 0 until this.columnCount) {
builder
.append(this.getColumnName(i))
.append("=")
.append(this.getStringOrNull(i))
if (i < this.columnCount - 1) {
builder.append(", ")
}
}
return builder.toString()
}