mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-21 10:17:56 +00:00
Use the PNP merging function for everything.
This commit is contained in:
@@ -47,15 +47,6 @@ data class PnpDataSet(
|
||||
for (operation in operations) {
|
||||
@Exhaustive
|
||||
when (operation) {
|
||||
is PnpOperation.Update -> {
|
||||
records.replace(operation.recipientId) { record ->
|
||||
record.copy(
|
||||
e164 = operation.e164,
|
||||
pni = operation.pni,
|
||||
serviceId = operation.aci ?: operation.pni
|
||||
)
|
||||
}
|
||||
}
|
||||
is PnpOperation.RemoveE164 -> {
|
||||
records.replace(operation.recipientId) { it.copy(e164 = null) }
|
||||
}
|
||||
@@ -145,8 +136,28 @@ data class PnpDataSet(
|
||||
*/
|
||||
data class PnpChangeSet(
|
||||
val id: PnpIdResolver,
|
||||
val operations: List<PnpOperation> = emptyList()
|
||||
)
|
||||
val operations: List<PnpOperation> = emptyList(),
|
||||
val breadCrumbs: List<String> = emptyList()
|
||||
) {
|
||||
// We want to exclude breadcrumbs from equality for testing purposes
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as PnpChangeSet
|
||||
|
||||
if (id != other.id) return false
|
||||
if (operations != other.operations) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = id.hashCode()
|
||||
result = 31 * result + operations.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
sealed class PnpIdResolver {
|
||||
data class PnpNoopId(
|
||||
@@ -165,33 +176,28 @@ sealed class PnpIdResolver {
|
||||
* Lets us describe various situations as a series of operations, making code clearer and tests easier.
|
||||
*/
|
||||
sealed class PnpOperation {
|
||||
data class Update(
|
||||
val recipientId: RecipientId,
|
||||
val e164: String?,
|
||||
val pni: PNI?,
|
||||
val aci: ACI?
|
||||
) : PnpOperation()
|
||||
abstract val recipientId: RecipientId
|
||||
|
||||
data class RemoveE164(
|
||||
val recipientId: RecipientId
|
||||
override val recipientId: RecipientId
|
||||
) : PnpOperation()
|
||||
|
||||
data class RemovePni(
|
||||
val recipientId: RecipientId
|
||||
override val recipientId: RecipientId
|
||||
) : PnpOperation()
|
||||
|
||||
data class SetE164(
|
||||
val recipientId: RecipientId,
|
||||
override val recipientId: RecipientId,
|
||||
val e164: String
|
||||
) : PnpOperation()
|
||||
|
||||
data class SetPni(
|
||||
val recipientId: RecipientId,
|
||||
override val recipientId: RecipientId,
|
||||
val pni: PNI
|
||||
) : PnpOperation()
|
||||
|
||||
data class SetAci(
|
||||
val recipientId: RecipientId,
|
||||
override val recipientId: RecipientId,
|
||||
val aci: ACI
|
||||
) : PnpOperation()
|
||||
|
||||
@@ -201,14 +207,17 @@ sealed class PnpOperation {
|
||||
data class Merge(
|
||||
val primaryId: RecipientId,
|
||||
val secondaryId: RecipientId
|
||||
) : PnpOperation()
|
||||
) : PnpOperation() {
|
||||
override val recipientId: RecipientId
|
||||
get() = throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
data class SessionSwitchoverInsert(
|
||||
val recipientId: RecipientId
|
||||
override val recipientId: RecipientId
|
||||
) : PnpOperation()
|
||||
|
||||
data class ChangeNumberInsert(
|
||||
val recipientId: RecipientId,
|
||||
override val recipientId: RecipientId,
|
||||
val oldE164: String,
|
||||
val newE164: String
|
||||
) : PnpOperation()
|
||||
|
||||
@@ -106,7 +106,6 @@ import org.whispersystems.signalservice.api.storage.SignalContactRecord
|
||||
import org.whispersystems.signalservice.api.storage.SignalGroupV1Record
|
||||
import org.whispersystems.signalservice.api.storage.SignalGroupV2Record
|
||||
import org.whispersystems.signalservice.api.storage.StorageId
|
||||
import org.whispersystems.signalservice.api.util.Preconditions
|
||||
import java.io.Closeable
|
||||
import java.io.IOException
|
||||
import java.util.Arrays
|
||||
@@ -421,7 +420,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
||||
|
||||
@JvmOverloads
|
||||
fun getAndPossiblyMerge(serviceId: ServiceId?, e164: String?, changeSelf: Boolean = false): RecipientId {
|
||||
return if (FeatureFlags.phoneNumberPrivacy()) {
|
||||
return if (FeatureFlags.recipientMergeV2()) {
|
||||
getAndPossiblyMergePnp(serviceId, e164, changeSelf)
|
||||
} else {
|
||||
getAndPossiblyMergeLegacy(serviceId, e164, changeSelf)
|
||||
@@ -525,15 +524,54 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
||||
fun getAndPossiblyMergePnp(serviceId: ServiceId?, e164: String?, changeSelf: Boolean = false): RecipientId {
|
||||
require(!(serviceId == null && e164 == null)) { "Must provide an ACI or E164!" }
|
||||
|
||||
return writableDatabase.withinTransaction {
|
||||
when {
|
||||
val db = writableDatabase
|
||||
var transactionSuccessful = false
|
||||
lateinit var result: ProcessPnpTupleResult
|
||||
|
||||
db.beginTransaction()
|
||||
try {
|
||||
result = when {
|
||||
serviceId is PNI -> processPnpTuple(e164, serviceId, null, pniVerified = false, changeSelf = changeSelf)
|
||||
serviceId is ACI -> processPnpTuple(e164, null, serviceId, pniVerified = false, changeSelf = changeSelf)
|
||||
serviceId == null -> processPnpTuple(e164, null, null, pniVerified = false, changeSelf = changeSelf)
|
||||
getByPni(PNI.from(serviceId.uuid())).isPresent -> processPnpTuple(e164, PNI.from(serviceId.uuid()), null, pniVerified = false, changeSelf = changeSelf)
|
||||
else -> processPnpTuple(e164, null, ACI.fromNullable(serviceId), pniVerified = false, changeSelf = changeSelf)
|
||||
}
|
||||
|
||||
if (result.operations.isNotEmpty()) {
|
||||
Log.i(TAG, "[getAndPossiblyMergePnp] BreadCrumbs: ${result.breadCrumbs}, Operations: ${result.operations}")
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful()
|
||||
transactionSuccessful = true
|
||||
} finally {
|
||||
db.endTransaction()
|
||||
|
||||
if (transactionSuccessful) {
|
||||
if (result.affectedIds.isNotEmpty()) {
|
||||
result.affectedIds.forEach { ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(it) }
|
||||
RetrieveProfileJob.enqueue(result.affectedIds)
|
||||
}
|
||||
|
||||
if (result.oldIds.isNotEmpty()) {
|
||||
result.oldIds.forEach { oldId ->
|
||||
Recipient.live(oldId).refresh(result.finalId)
|
||||
ApplicationDependencies.getRecipientCache().remap(oldId, result.finalId)
|
||||
}
|
||||
}
|
||||
|
||||
if (result.affectedIds.isNotEmpty() || result.oldIds.isNotEmpty()) {
|
||||
StorageSyncHelper.scheduleSyncForDataChange()
|
||||
RecipientId.clearCache()
|
||||
}
|
||||
|
||||
if (result.changedNumberId != null) {
|
||||
ApplicationDependencies.getJobManager().add(RecipientChangedNumberJob(result.changedNumberId!!))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result.finalId
|
||||
}
|
||||
|
||||
fun getAllServiceIdProfileKeyPairs(): Map<ServiceId, ProfileKey> {
|
||||
@@ -2190,7 +2228,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
||||
db.beginTransaction()
|
||||
try {
|
||||
for ((e164, result) in mapping) {
|
||||
ids += processPnpTuple(e164, result.pni, result.aci, false)
|
||||
ids += processPnpTuple(e164, result.pni, result.aci, false).finalId
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful()
|
||||
@@ -2208,26 +2246,49 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
||||
* @return The [RecipientId] of the resulting recipient.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
fun processPnpTuple(e164: String?, pni: PNI?, aci: ACI?, pniVerified: Boolean, changeSelf: Boolean = false): RecipientId {
|
||||
val changeSet = processPnpTupleToChangeSet(e164, pni, aci, pniVerified, changeSelf)
|
||||
return writePnpChangeSetToDisk(changeSet)
|
||||
}
|
||||
fun processPnpTuple(e164: String?, pni: PNI?, aci: ACI?, pniVerified: Boolean, changeSelf: Boolean = false, pnpEnabled: Boolean = FeatureFlags.phoneNumberPrivacy()): ProcessPnpTupleResult {
|
||||
val changeSet: PnpChangeSet = processPnpTupleToChangeSet(e164, pni, aci, pniVerified, changeSelf)
|
||||
|
||||
val affectedIds: MutableSet<RecipientId> = mutableSetOf()
|
||||
val oldIds: MutableSet<RecipientId> = mutableSetOf()
|
||||
var changedNumberId: RecipientId? = null
|
||||
|
||||
@VisibleForTesting
|
||||
fun writePnpChangeSetToDisk(changeSet: PnpChangeSet): RecipientId {
|
||||
for (operation in changeSet.operations) {
|
||||
@Exhaustive
|
||||
when (operation) {
|
||||
is PnpOperation.Update -> {
|
||||
writableDatabase.update(TABLE_NAME)
|
||||
.values(
|
||||
PHONE to operation.e164,
|
||||
SERVICE_ID to (operation.aci ?: operation.pni).toString(),
|
||||
PNI_COLUMN to operation.pni.toString()
|
||||
)
|
||||
.where("$ID = ?", operation.recipientId)
|
||||
.run()
|
||||
is PnpOperation.RemoveE164,
|
||||
is PnpOperation.RemovePni,
|
||||
is PnpOperation.SetAci,
|
||||
is PnpOperation.SetE164,
|
||||
is PnpOperation.SetPni -> {
|
||||
affectedIds.add(operation.recipientId)
|
||||
}
|
||||
is PnpOperation.Merge -> {
|
||||
oldIds.add(operation.secondaryId)
|
||||
affectedIds.add(operation.primaryId)
|
||||
}
|
||||
is PnpOperation.SessionSwitchoverInsert -> {}
|
||||
is PnpOperation.ChangeNumberInsert -> changedNumberId = operation.recipientId
|
||||
}
|
||||
}
|
||||
|
||||
val finalId: RecipientId = writePnpChangeSetToDisk(changeSet, pnpEnabled)
|
||||
|
||||
return ProcessPnpTupleResult(
|
||||
finalId = finalId,
|
||||
affectedIds = affectedIds,
|
||||
oldIds = oldIds,
|
||||
changedNumberId = changedNumberId,
|
||||
operations = changeSet.operations,
|
||||
breadCrumbs = changeSet.breadCrumbs
|
||||
)
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
fun writePnpChangeSetToDisk(changeSet: PnpChangeSet, pnpEnabled: Boolean): RecipientId {
|
||||
for (operation in changeSet.operations) {
|
||||
@Exhaustive
|
||||
when (operation) {
|
||||
is PnpOperation.RemoveE164 -> {
|
||||
writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
@@ -2282,26 +2343,34 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
||||
.run()
|
||||
}
|
||||
is PnpOperation.Merge -> {
|
||||
Log.w(TAG, "WARNING: Performing a PNP merge! This operation currently only has a basic implementation only suitable for basic testing!")
|
||||
|
||||
val primary = getRecord(operation.primaryId)
|
||||
val secondary = getRecord(operation.secondaryId)
|
||||
|
||||
writableDatabase
|
||||
.delete(TABLE_NAME)
|
||||
.where("$ID = ?", operation.secondaryId)
|
||||
.run()
|
||||
if (primary.serviceId != null && !primary.sidIsPni() && secondary.e164 != null) {
|
||||
merge(operation.primaryId, operation.secondaryId)
|
||||
} else {
|
||||
if (!pnpEnabled) {
|
||||
throw AssertionError("This type of merge is not supported in production!")
|
||||
}
|
||||
|
||||
writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
.values(
|
||||
PHONE to (primary.e164 ?: secondary.e164),
|
||||
PNI_COLUMN to (primary.pni ?: secondary.pni)?.toString(),
|
||||
SERVICE_ID to (primary.serviceId ?: secondary.serviceId)?.toString(),
|
||||
REGISTERED to RegisteredState.REGISTERED.id
|
||||
)
|
||||
.where("$ID = ?", operation.primaryId)
|
||||
.run()
|
||||
Log.w(TAG, "WARNING: Performing an unfinished PNP merge! This operation currently only has a basic implementation only suitable for basic testing!")
|
||||
|
||||
writableDatabase
|
||||
.delete(TABLE_NAME)
|
||||
.where("$ID = ?", operation.secondaryId)
|
||||
.run()
|
||||
|
||||
writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
.values(
|
||||
PHONE to (primary.e164 ?: secondary.e164),
|
||||
PNI_COLUMN to (primary.pni ?: secondary.pni)?.toString(),
|
||||
SERVICE_ID to (primary.serviceId ?: secondary.serviceId)?.toString(),
|
||||
REGISTERED to RegisteredState.REGISTERED.id
|
||||
)
|
||||
.where("$ID = ?", operation.primaryId)
|
||||
.run()
|
||||
}
|
||||
}
|
||||
is PnpOperation.SessionSwitchoverInsert -> {
|
||||
// TODO [pnp]
|
||||
@@ -2334,8 +2403,10 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
||||
*/
|
||||
@VisibleForTesting
|
||||
fun processPnpTupleToChangeSet(e164: String?, pni: PNI?, aci: ACI?, pniVerified: Boolean, changeSelf: Boolean = false): PnpChangeSet {
|
||||
Preconditions.checkArgument(e164 != null || pni != null || aci != null, "Must provide at least one field!")
|
||||
Preconditions.checkArgument(pni == null || e164 != null, "If a PNI is provided, you must also provide an E164!")
|
||||
check(e164 != null || pni != null || aci != null) { "Must provide at least one field!" }
|
||||
check(pni == null || e164 != null) { "If a PNI is provided, you must also provide an E164!" }
|
||||
|
||||
val breadCrumbs: MutableList<String> = mutableListOf()
|
||||
|
||||
val partialData = PnpDataSet(
|
||||
e164 = e164,
|
||||
@@ -2347,91 +2418,44 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
||||
byAciSid = aci?.let { getByServiceId(it).orElse(null) }
|
||||
)
|
||||
|
||||
val allRequiredDbFields: List<RecipientId?> = if (aci != null) {
|
||||
listOf(partialData.byE164, partialData.byAciSid, partialData.byPniOnly)
|
||||
} else {
|
||||
listOf(partialData.byE164, partialData.byPniSid, partialData.byPniOnly)
|
||||
val allRequiredDbFields: MutableList<RecipientId?> = mutableListOf()
|
||||
if (e164 != null) {
|
||||
allRequiredDbFields += partialData.byE164
|
||||
}
|
||||
if (aci != null) {
|
||||
allRequiredDbFields += partialData.byAciSid
|
||||
}
|
||||
if (pni != null) {
|
||||
allRequiredDbFields += partialData.byPniOnly
|
||||
}
|
||||
if (pni != null && aci == null) {
|
||||
allRequiredDbFields += partialData.byPniSid
|
||||
}
|
||||
|
||||
val allRequiredDbFieldPopulated: Boolean = allRequiredDbFields.all { it != null }
|
||||
|
||||
// All IDs agree and the database is up-to-date
|
||||
if (partialData.commonId != null && allRequiredDbFieldPopulated) {
|
||||
return PnpChangeSet(id = PnpIdResolver.PnpNoopId(partialData.commonId))
|
||||
breadCrumbs.add("CommonIdAndUpToDate")
|
||||
return PnpChangeSet(id = PnpIdResolver.PnpNoopId(partialData.commonId), breadCrumbs = breadCrumbs)
|
||||
}
|
||||
|
||||
// All ID's agree, but we need to update the database
|
||||
if (partialData.commonId != null && !allRequiredDbFieldPopulated) {
|
||||
val record: RecipientRecord = getRecord(partialData.commonId)
|
||||
|
||||
val operations: MutableList<PnpOperation> = mutableListOf()
|
||||
|
||||
// This is a special case. The ACI passed in doesn't match the common record. We can't change ACIs, so we need to make a new record.
|
||||
if (aci != null && aci != record.serviceId && record.serviceId != null && !record.sidIsPni()) {
|
||||
if (record.e164 == e164) {
|
||||
operations += PnpOperation.RemoveE164(record.id)
|
||||
operations += PnpOperation.RemovePni(record.id)
|
||||
} else if (record.pni == pni) {
|
||||
operations += PnpOperation.RemovePni(record.id)
|
||||
}
|
||||
|
||||
return PnpChangeSet(
|
||||
id = PnpIdResolver.PnpInsert(e164, pni, aci),
|
||||
operations = operations
|
||||
)
|
||||
}
|
||||
|
||||
var updatedNumber = false
|
||||
if (e164 != null && record.e164 != e164 && (changeSelf || aci != SignalStore.account().aci)) {
|
||||
operations += PnpOperation.SetE164(
|
||||
recipientId = partialData.commonId,
|
||||
e164 = e164
|
||||
)
|
||||
updatedNumber = true
|
||||
}
|
||||
|
||||
if (pni != null && record.pni != pni) {
|
||||
operations += PnpOperation.SetPni(
|
||||
recipientId = partialData.commonId,
|
||||
pni = pni
|
||||
)
|
||||
}
|
||||
|
||||
if (aci != null && record.serviceId != aci) {
|
||||
operations += PnpOperation.SetAci(
|
||||
recipientId = partialData.commonId,
|
||||
aci = aci
|
||||
)
|
||||
}
|
||||
|
||||
if (record.e164 != null && updatedNumber) {
|
||||
operations += PnpOperation.ChangeNumberInsert(
|
||||
recipientId = partialData.commonId,
|
||||
oldE164 = record.e164,
|
||||
newE164 = e164!!
|
||||
)
|
||||
}
|
||||
|
||||
val newServiceId: ServiceId? = aci ?: pni ?: record.serviceId
|
||||
|
||||
if (!pniVerified && record.serviceId != null && record.serviceId != newServiceId && sessions.hasAnySessionFor(record.serviceId.toString())) {
|
||||
operations += PnpOperation.SessionSwitchoverInsert(partialData.commonId)
|
||||
}
|
||||
|
||||
return PnpChangeSet(
|
||||
id = PnpIdResolver.PnpNoopId(partialData.commonId),
|
||||
operations = operations
|
||||
)
|
||||
breadCrumbs.add("CommonIdButNeedsUpdate")
|
||||
return processNonMergePnpUpdate(e164, pni, aci, commonId = partialData.commonId, pniVerified = pniVerified, changeSelf = changeSelf, breadCrumbs = breadCrumbs)
|
||||
}
|
||||
|
||||
// Nothing matches
|
||||
if (partialData.byE164 == null && partialData.byPniSid == null && partialData.byAciSid == null) {
|
||||
breadCrumbs += "NothingMatches"
|
||||
return PnpChangeSet(
|
||||
id = PnpIdResolver.PnpInsert(
|
||||
e164 = e164,
|
||||
pni = pni,
|
||||
aci = aci
|
||||
)
|
||||
),
|
||||
breadCrumbs = breadCrumbs
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2444,32 +2468,36 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
||||
// It may be that some data just gets shuffled around, or it may be that
|
||||
// two or more records get merged into one record, with the others being deleted.
|
||||
|
||||
breadCrumbs += "NeedsMerge"
|
||||
|
||||
val fullData = partialData.copy(
|
||||
e164Record = partialData.byE164?.let { getRecord(it) },
|
||||
pniSidRecord = partialData.byPniSid?.let { getRecord(it) },
|
||||
aciSidRecord = partialData.byAciSid?.let { getRecord(it) },
|
||||
)
|
||||
|
||||
Preconditions.checkState(fullData.commonId == null)
|
||||
Preconditions.checkState(listOfNotNull(fullData.byE164, fullData.byPniSid, fullData.byPniOnly, fullData.byAciSid).size >= 2)
|
||||
check(fullData.commonId == null)
|
||||
check(listOfNotNull(fullData.byE164, fullData.byPniSid, fullData.byPniOnly, fullData.byAciSid).size >= 2)
|
||||
|
||||
val operations: MutableList<PnpOperation> = mutableListOf()
|
||||
|
||||
operations += processPossibleE164PniSidMerge(pni, pniVerified, fullData)
|
||||
operations += processPossiblePniSidAciSidMerge(e164, pni, aci, fullData.perform(operations))
|
||||
operations += processPossibleE164AciSidMerge(e164, pni, aci, fullData.perform(operations))
|
||||
operations += processPossibleE164PniSidMerge(pni, pniVerified, fullData, breadCrumbs)
|
||||
operations += processPossiblePniSidAciSidMerge(e164, pni, aci, fullData.perform(operations), changeSelf, breadCrumbs)
|
||||
operations += processPossibleE164AciSidMerge(e164, pni, aci, fullData.perform(operations), changeSelf, breadCrumbs)
|
||||
|
||||
val finalData: PnpDataSet = fullData.perform(operations)
|
||||
val primaryId: RecipientId = listOfNotNull(finalData.byAciSid, finalData.byE164, finalData.byPniSid).first()
|
||||
|
||||
if (finalData.byAciSid == null && aci != null) {
|
||||
breadCrumbs += "FinalUpdateAci"
|
||||
operations += PnpOperation.SetAci(
|
||||
recipientId = primaryId,
|
||||
aci = aci
|
||||
)
|
||||
}
|
||||
|
||||
if (finalData.byE164 == null && e164 != null && (changeSelf || aci != SignalStore.account().aci)) {
|
||||
if (finalData.byE164 == null && e164 != null && (changeSelf || notSelf(e164, pni, aci))) {
|
||||
breadCrumbs += "FinalUpdateE164"
|
||||
operations += PnpOperation.SetE164(
|
||||
recipientId = primaryId,
|
||||
e164 = e164
|
||||
@@ -2477,6 +2505,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
||||
}
|
||||
|
||||
if (finalData.byPniSid == null && finalData.byPniOnly == null && pni != null) {
|
||||
breadCrumbs += "FinalUpdatePni"
|
||||
operations += PnpOperation.SetPni(
|
||||
recipientId = primaryId,
|
||||
pni = pni
|
||||
@@ -2485,21 +2514,109 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
||||
|
||||
return PnpChangeSet(
|
||||
id = PnpIdResolver.PnpNoopId(primaryId),
|
||||
operations = operations
|
||||
operations = operations,
|
||||
breadCrumbs = breadCrumbs
|
||||
)
|
||||
}
|
||||
|
||||
private fun processPossibleE164PniSidMerge(pni: PNI?, pniVerified: Boolean, data: PnpDataSet): List<PnpOperation> {
|
||||
private fun notSelf(e164: String?, pni: PNI?, aci: ACI?): Boolean {
|
||||
return (e164 == null || e164 != SignalStore.account().e164) &&
|
||||
(pni == null || pni != SignalStore.account().pni) &&
|
||||
(aci == null || aci != SignalStore.account().aci)
|
||||
}
|
||||
|
||||
private fun isSelf(e164: String?, pni: PNI?, aci: ACI?): Boolean {
|
||||
return (e164 != null && e164 == SignalStore.account().e164) ||
|
||||
(pni != null && pni == SignalStore.account().pni) ||
|
||||
(aci != null && aci == SignalStore.account().aci)
|
||||
}
|
||||
|
||||
private fun processNonMergePnpUpdate(e164: String?, pni: PNI?, aci: ACI?, pniVerified: Boolean, changeSelf: Boolean, commonId: RecipientId, breadCrumbs: MutableList<String>): PnpChangeSet {
|
||||
val record: RecipientRecord = getRecord(commonId)
|
||||
|
||||
val operations: MutableList<PnpOperation> = mutableListOf()
|
||||
|
||||
// This is a special case. The ACI passed in doesn't match the common record. We can't change ACIs, so we need to make a new record.
|
||||
if (aci != null && aci != record.serviceId && record.serviceId != null && !record.sidIsPni()) {
|
||||
breadCrumbs += "AciDoesNotMatchCommonRecord"
|
||||
|
||||
if (record.e164 == e164 && (changeSelf || notSelf(e164, pni, aci))) {
|
||||
breadCrumbs += "StealingE164"
|
||||
operations += PnpOperation.RemoveE164(record.id)
|
||||
operations += PnpOperation.RemovePni(record.id)
|
||||
} else if (record.pni == pni) {
|
||||
breadCrumbs += "StealingPni"
|
||||
operations += PnpOperation.RemovePni(record.id)
|
||||
}
|
||||
|
||||
val insertE164: String? = if (changeSelf || notSelf(e164, pni, aci)) e164 else null
|
||||
val insertPni: PNI? = if (changeSelf || notSelf(e164, pni, aci)) pni else null
|
||||
|
||||
return PnpChangeSet(
|
||||
id = PnpIdResolver.PnpInsert(insertE164, insertPni, aci),
|
||||
operations = operations,
|
||||
breadCrumbs = breadCrumbs
|
||||
)
|
||||
}
|
||||
|
||||
var updatedNumber = false
|
||||
if (e164 != null && record.e164 != e164 && (changeSelf || notSelf(e164, pni, aci))) {
|
||||
operations += PnpOperation.SetE164(
|
||||
recipientId = commonId,
|
||||
e164 = e164
|
||||
)
|
||||
updatedNumber = true
|
||||
}
|
||||
|
||||
if (pni != null && record.pni != pni) {
|
||||
operations += PnpOperation.SetPni(
|
||||
recipientId = commonId,
|
||||
pni = pni
|
||||
)
|
||||
}
|
||||
|
||||
if (aci != null && record.serviceId != aci) {
|
||||
operations += PnpOperation.SetAci(
|
||||
recipientId = commonId,
|
||||
aci = aci
|
||||
)
|
||||
}
|
||||
|
||||
if (record.e164 != null && updatedNumber) {
|
||||
operations += PnpOperation.ChangeNumberInsert(
|
||||
recipientId = commonId,
|
||||
oldE164 = record.e164,
|
||||
newE164 = e164!!
|
||||
)
|
||||
}
|
||||
|
||||
val newServiceId: ServiceId? = aci ?: pni ?: record.serviceId
|
||||
|
||||
if (!pniVerified && record.serviceId != null && record.serviceId != newServiceId && sessions.hasAnySessionFor(record.serviceId.toString())) {
|
||||
operations += PnpOperation.SessionSwitchoverInsert(commonId)
|
||||
}
|
||||
|
||||
return PnpChangeSet(
|
||||
id = PnpIdResolver.PnpNoopId(commonId),
|
||||
operations = operations,
|
||||
breadCrumbs = breadCrumbs
|
||||
)
|
||||
}
|
||||
|
||||
private fun processPossibleE164PniSidMerge(pni: PNI?, pniVerified: Boolean, data: PnpDataSet, breadCrumbs: MutableList<String>): List<PnpOperation> {
|
||||
if (pni == null || data.byE164 == null || data.byPniSid == null || data.e164Record == null || data.pniSidRecord == null || data.e164Record.id == data.pniSidRecord.id) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
// We have found records for both the E164 and PNI, and they're different
|
||||
breadCrumbs += "E164PniSidMerge"
|
||||
|
||||
val operations: MutableList<PnpOperation> = mutableListOf()
|
||||
|
||||
// The PNI record only has a single identifier. We know we must merge.
|
||||
if (data.pniSidRecord.sidOnly(pni)) {
|
||||
breadCrumbs += "PniOnly"
|
||||
|
||||
if (data.e164Record.pni != null) {
|
||||
operations += PnpOperation.RemovePni(data.byE164)
|
||||
}
|
||||
@@ -2511,8 +2628,9 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
||||
|
||||
// TODO: Possible session switchover?
|
||||
} else {
|
||||
Preconditions.checkState(!data.pniSidRecord.pniAndAci())
|
||||
Preconditions.checkState(data.pniSidRecord.e164 != null)
|
||||
check(!data.pniSidRecord.pniAndAci() && data.pniSidRecord.e164 != null)
|
||||
|
||||
breadCrumbs += "PniSidRecordHasE164"
|
||||
|
||||
operations += PnpOperation.RemovePni(data.byPniSid)
|
||||
operations += PnpOperation.SetPni(
|
||||
@@ -2532,17 +2650,25 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
||||
return operations
|
||||
}
|
||||
|
||||
private fun processPossiblePniSidAciSidMerge(e164: String?, pni: PNI?, aci: ACI?, data: PnpDataSet): List<PnpOperation> {
|
||||
private fun processPossiblePniSidAciSidMerge(e164: String?, pni: PNI?, aci: ACI?, data: PnpDataSet, changeSelf: Boolean, breadCrumbs: MutableList<String>): List<PnpOperation> {
|
||||
if (pni == null || aci == null || data.byPniSid == null || data.byAciSid == null || data.pniSidRecord == null || data.aciSidRecord == null || data.pniSidRecord.id == data.aciSidRecord.id) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
if (!changeSelf && isSelf(e164, pni, aci)) {
|
||||
breadCrumbs += "ChangeSelfPreventsPniSidAciSidMerge"
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
// We have found records for both the PNI and ACI, and they're different
|
||||
breadCrumbs += "PniSidAciSidMerge"
|
||||
|
||||
val operations: MutableList<PnpOperation> = mutableListOf()
|
||||
|
||||
// The PNI record only has a single identifier. We know we must merge.
|
||||
if (data.pniSidRecord.sidOnly(pni)) {
|
||||
breadCrumbs += "PniOnly"
|
||||
|
||||
if (data.aciSidRecord.pni != null) {
|
||||
operations += PnpOperation.RemovePni(data.byAciSid)
|
||||
}
|
||||
@@ -2554,6 +2680,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
||||
} else if (data.pniSidRecord.e164 == e164) {
|
||||
// The PNI record also has the E164 on it. We're going to be stealing both fields,
|
||||
// so this is basically a merge with a little bit of extra prep.
|
||||
breadCrumbs += "PniSidRecordHasMatchingE164"
|
||||
|
||||
if (data.aciSidRecord.pni != null) {
|
||||
operations += PnpOperation.RemovePni(data.byAciSid)
|
||||
@@ -2576,33 +2703,55 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Preconditions.checkState(data.pniSidRecord.e164 != null && data.pniSidRecord.e164 != e164)
|
||||
check(data.pniSidRecord.e164 != null && data.pniSidRecord.e164 != e164)
|
||||
breadCrumbs += "PniSidRecordHasNonMatchingE164"
|
||||
|
||||
operations += PnpOperation.RemovePni(data.byPniSid)
|
||||
|
||||
operations += PnpOperation.Update(
|
||||
recipientId = data.byAciSid,
|
||||
e164 = e164,
|
||||
pni = pni,
|
||||
aci = ACI.from(data.aciSidRecord.serviceId)
|
||||
)
|
||||
if (data.aciSidRecord.pni != pni) {
|
||||
operations += PnpOperation.SetPni(
|
||||
recipientId = data.byAciSid,
|
||||
pni = pni
|
||||
)
|
||||
}
|
||||
|
||||
if (e164 != null && data.aciSidRecord.e164 != e164) {
|
||||
operations += PnpOperation.SetE164(
|
||||
recipientId = data.byAciSid,
|
||||
e164 = e164
|
||||
)
|
||||
|
||||
if (data.aciSidRecord.e164 != null) {
|
||||
operations += PnpOperation.ChangeNumberInsert(
|
||||
recipientId = data.byAciSid,
|
||||
oldE164 = data.aciSidRecord.e164,
|
||||
newE164 = e164
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return operations
|
||||
}
|
||||
|
||||
private fun processPossibleE164AciSidMerge(e164: String?, pni: PNI?, aci: ACI?, data: PnpDataSet): List<PnpOperation> {
|
||||
private fun processPossibleE164AciSidMerge(e164: String?, pni: PNI?, aci: ACI?, data: PnpDataSet, changeSelf: Boolean, breadCrumbs: MutableList<String>): List<PnpOperation> {
|
||||
if (e164 == null || aci == null || data.byE164 == null || data.byAciSid == null || data.e164Record == null || data.aciSidRecord == null || data.e164Record.id == data.aciSidRecord.id) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
if (!changeSelf && isSelf(e164, pni, aci)) {
|
||||
breadCrumbs += "ChangeSelfPreventsE164AciSidMerge"
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
// We have found records for both the E164 and ACI, and they're different
|
||||
breadCrumbs += "E164AciSidMerge"
|
||||
|
||||
val operations: MutableList<PnpOperation> = mutableListOf()
|
||||
|
||||
// The PNI record only has a single identifier. We know we must merge.
|
||||
// The E164 record only has a single identifier. We know we must merge.
|
||||
if (data.e164Record.e164Only()) {
|
||||
// TODO high trust
|
||||
breadCrumbs += "E164Only"
|
||||
|
||||
if (data.aciSidRecord.e164 != null && data.aciSidRecord.e164 != e164) {
|
||||
operations += PnpOperation.RemoveE164(data.byAciSid)
|
||||
@@ -2623,6 +2772,8 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
||||
} else if (data.e164Record.pni != null && data.e164Record.pni == pni) {
|
||||
// The E164 record also has the PNI on it. We're going to be stealing both fields,
|
||||
// so this is basically a merge with a little bit of extra prep.
|
||||
breadCrumbs += "E164RecordHasMatchingPni"
|
||||
|
||||
if (data.aciSidRecord.pni != null) {
|
||||
operations += PnpOperation.RemovePni(data.byAciSid)
|
||||
}
|
||||
@@ -2644,6 +2795,9 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
||||
)
|
||||
}
|
||||
} else {
|
||||
check(data.e164Record.pni == null || data.e164Record.pni != pni)
|
||||
breadCrumbs += "E164RecordHasNonMatchingPni"
|
||||
|
||||
operations += PnpOperation.RemoveE164(data.byE164)
|
||||
|
||||
operations += PnpOperation.SetE164(
|
||||
@@ -3332,32 +3486,34 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
||||
db.delete(TABLE_NAME, ID_WHERE, SqlUtil.buildArgs(byE164))
|
||||
RemappedRecords.getInstance().addRecipient(byE164, byAci)
|
||||
|
||||
val uuidValues = ContentValues().apply {
|
||||
put(PHONE, e164Record.e164)
|
||||
put(BLOCKED, e164Record.isBlocked || aciRecord.isBlocked)
|
||||
put(MESSAGE_RINGTONE, Optional.ofNullable(aciRecord.messageRingtone).or(Optional.ofNullable(e164Record.messageRingtone)).map { obj: Uri? -> obj.toString() }.orElse(null))
|
||||
put(MESSAGE_VIBRATE, if (aciRecord.messageVibrateState != VibrateState.DEFAULT) aciRecord.messageVibrateState.id else e164Record.messageVibrateState.id)
|
||||
put(CALL_RINGTONE, Optional.ofNullable(aciRecord.callRingtone).or(Optional.ofNullable(e164Record.callRingtone)).map { obj: Uri? -> obj.toString() }.orElse(null))
|
||||
put(CALL_VIBRATE, if (aciRecord.callVibrateState != VibrateState.DEFAULT) aciRecord.callVibrateState.id else e164Record.callVibrateState.id)
|
||||
put(NOTIFICATION_CHANNEL, aciRecord.notificationChannel ?: e164Record.notificationChannel)
|
||||
put(MUTE_UNTIL, if (aciRecord.muteUntil > 0) aciRecord.muteUntil else e164Record.muteUntil)
|
||||
put(CHAT_COLORS, Optional.ofNullable(aciRecord.chatColors).or(Optional.ofNullable(e164Record.chatColors)).map { colors: ChatColors? -> colors!!.serialize().toByteArray() }.orElse(null))
|
||||
put(AVATAR_COLOR, aciRecord.avatarColor.serialize())
|
||||
put(CUSTOM_CHAT_COLORS_ID, Optional.ofNullable(aciRecord.chatColors).or(Optional.ofNullable(e164Record.chatColors)).map { colors: ChatColors? -> colors!!.id.longValue }.orElse(null))
|
||||
put(SEEN_INVITE_REMINDER, e164Record.insightsBannerTier.id)
|
||||
put(DEFAULT_SUBSCRIPTION_ID, e164Record.getDefaultSubscriptionId().orElse(-1))
|
||||
put(MESSAGE_EXPIRATION_TIME, if (aciRecord.expireMessages > 0) aciRecord.expireMessages else e164Record.expireMessages)
|
||||
put(REGISTERED, RegisteredState.REGISTERED.id)
|
||||
put(SYSTEM_GIVEN_NAME, e164Record.systemProfileName.givenName)
|
||||
put(SYSTEM_FAMILY_NAME, e164Record.systemProfileName.familyName)
|
||||
put(SYSTEM_JOINED_NAME, e164Record.systemProfileName.toString())
|
||||
put(SYSTEM_PHOTO_URI, e164Record.systemContactPhotoUri)
|
||||
put(SYSTEM_PHONE_LABEL, e164Record.systemPhoneLabel)
|
||||
put(SYSTEM_CONTACT_URI, e164Record.systemContactUri)
|
||||
put(PROFILE_SHARING, aciRecord.profileSharing || e164Record.profileSharing)
|
||||
put(CAPABILITIES, max(aciRecord.rawCapabilities, e164Record.rawCapabilities))
|
||||
put(MENTION_SETTING, if (aciRecord.mentionSetting != MentionSetting.ALWAYS_NOTIFY) aciRecord.mentionSetting.id else e164Record.mentionSetting.id)
|
||||
}
|
||||
// TODO [pnp] We should pass in the PNI involved in the merge and prefer that over either of the ones in the records
|
||||
val uuidValues = contentValuesOf(
|
||||
PHONE to e164Record.e164,
|
||||
PNI_COLUMN to (e164Record.pni ?: aciRecord.pni)?.toString(),
|
||||
BLOCKED to (e164Record.isBlocked || aciRecord.isBlocked),
|
||||
MESSAGE_RINGTONE to Optional.ofNullable(aciRecord.messageRingtone).or(Optional.ofNullable(e164Record.messageRingtone)).map { obj: Uri? -> obj.toString() }.orElse(null),
|
||||
MESSAGE_VIBRATE to if (aciRecord.messageVibrateState != VibrateState.DEFAULT) aciRecord.messageVibrateState.id else e164Record.messageVibrateState.id,
|
||||
CALL_RINGTONE to Optional.ofNullable(aciRecord.callRingtone).or(Optional.ofNullable(e164Record.callRingtone)).map { obj: Uri? -> obj.toString() }.orElse(null),
|
||||
CALL_VIBRATE to if (aciRecord.callVibrateState != VibrateState.DEFAULT) aciRecord.callVibrateState.id else e164Record.callVibrateState.id,
|
||||
NOTIFICATION_CHANNEL to (aciRecord.notificationChannel ?: e164Record.notificationChannel),
|
||||
MUTE_UNTIL to if (aciRecord.muteUntil > 0) aciRecord.muteUntil else e164Record.muteUntil,
|
||||
CHAT_COLORS to Optional.ofNullable(aciRecord.chatColors).or(Optional.ofNullable(e164Record.chatColors)).map { colors: ChatColors? -> colors!!.serialize().toByteArray() }.orElse(null),
|
||||
AVATAR_COLOR to aciRecord.avatarColor.serialize(),
|
||||
CUSTOM_CHAT_COLORS_ID to Optional.ofNullable(aciRecord.chatColors).or(Optional.ofNullable(e164Record.chatColors)).map { colors: ChatColors? -> colors!!.id.longValue }.orElse(null),
|
||||
SEEN_INVITE_REMINDER to e164Record.insightsBannerTier.id,
|
||||
DEFAULT_SUBSCRIPTION_ID to e164Record.getDefaultSubscriptionId().orElse(-1),
|
||||
MESSAGE_EXPIRATION_TIME to if (aciRecord.expireMessages > 0) aciRecord.expireMessages else e164Record.expireMessages,
|
||||
REGISTERED to RegisteredState.REGISTERED.id,
|
||||
SYSTEM_GIVEN_NAME to e164Record.systemProfileName.givenName,
|
||||
SYSTEM_FAMILY_NAME to e164Record.systemProfileName.familyName,
|
||||
SYSTEM_JOINED_NAME to e164Record.systemProfileName.toString(),
|
||||
SYSTEM_PHOTO_URI to e164Record.systemContactPhotoUri,
|
||||
SYSTEM_PHONE_LABEL to e164Record.systemPhoneLabel,
|
||||
SYSTEM_CONTACT_URI to e164Record.systemContactUri,
|
||||
PROFILE_SHARING to (aciRecord.profileSharing || e164Record.profileSharing),
|
||||
CAPABILITIES to max(aciRecord.rawCapabilities, e164Record.rawCapabilities),
|
||||
MENTION_SETTING to if (aciRecord.mentionSetting != MentionSetting.ALWAYS_NOTIFY) aciRecord.mentionSetting.id else e164Record.mentionSetting.id
|
||||
)
|
||||
|
||||
if (aciRecord.profileKey != null) {
|
||||
updateProfileValuesForMerge(uuidValues, aciRecord)
|
||||
@@ -4098,4 +4254,13 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class ProcessPnpTupleResult(
|
||||
val finalId: RecipientId,
|
||||
val affectedIds: Set<RecipientId>,
|
||||
val oldIds: Set<RecipientId>,
|
||||
val changedNumberId: RecipientId?,
|
||||
val operations: List<PnpOperation>,
|
||||
val breadCrumbs: List<String>,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ public final class LiveRecipient {
|
||||
details = RecipientDetails.forIndividual(context, record);
|
||||
}
|
||||
|
||||
Recipient recipient = new Recipient(id, details, true);
|
||||
Recipient recipient = new Recipient(record.getId(), details, true);
|
||||
RecipientIdCache.INSTANCE.put(recipient);
|
||||
return recipient;
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ public final class FeatureFlags {
|
||||
private static final String TELECOM_MANUFACTURER_ALLOWLIST = "android.calling.telecomAllowList";
|
||||
private static final String TELECOM_MODEL_BLOCKLIST = "android.calling.telecomModelBlockList";
|
||||
private static final String CAMERAX_MODEL_BLOCKLIST = "android.cameraXModelBlockList";
|
||||
private static final String RECIPIENT_MERGE_V2 = "android.recipientMergeV2";
|
||||
|
||||
/**
|
||||
* We will only store remote values for flags in this set. If you want a flag to be controllable
|
||||
@@ -152,7 +153,8 @@ public final class FeatureFlags {
|
||||
GIFT_BADGE_SEND_SUPPORT,
|
||||
TELECOM_MANUFACTURER_ALLOWLIST,
|
||||
TELECOM_MODEL_BLOCKLIST,
|
||||
CAMERAX_MODEL_BLOCKLIST
|
||||
CAMERAX_MODEL_BLOCKLIST,
|
||||
RECIPIENT_MERGE_V2
|
||||
);
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -214,7 +216,8 @@ public final class FeatureFlags {
|
||||
USE_FCM_FOREGROUND_SERVICE,
|
||||
TELECOM_MANUFACTURER_ALLOWLIST,
|
||||
TELECOM_MODEL_BLOCKLIST,
|
||||
CAMERAX_MODEL_BLOCKLIST
|
||||
CAMERAX_MODEL_BLOCKLIST,
|
||||
RECIPIENT_MERGE_V2
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -535,6 +538,13 @@ public final class FeatureFlags {
|
||||
return giftBadgeReceiveSupport() && getBoolean(GIFT_BADGE_SEND_SUPPORT, Environment.IS_STAGING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not we should use the new recipient merging strategy.
|
||||
*/
|
||||
public static boolean recipientMergeV2() {
|
||||
return getBoolean(RECIPIENT_MERGE_V2, false);
|
||||
}
|
||||
|
||||
/** Only for rendering debug info. */
|
||||
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
|
||||
return new TreeMap<>(REMOTE_VALUES);
|
||||
|
||||
Reference in New Issue
Block a user