Improve contact pull-to-refresh performance.

This commit is contained in:
Greyson Parrelli
2022-10-27 15:06:45 -04:00
committed by Cody Henthorne
parent 2cfa685ae2
commit 4077dc829a
10 changed files with 229 additions and 89 deletions

View File

@@ -1,24 +1,19 @@
package org.thoughtcrime.securesms.contacts.sync
import android.Manifest
import android.accounts.Account
import android.content.Context
import android.content.OperationApplicationException
import android.os.RemoteException
import android.text.TextUtils
import androidx.annotation.WorkerThread
import org.signal.contacts.ContactLinkConfiguration
import org.signal.contacts.SystemContactsRepository
import org.signal.contacts.SystemContactsRepository.ContactIterator
import org.signal.contacts.SystemContactsRepository.ContactPhoneDetails
import org.signal.core.util.Stopwatch
import org.signal.core.util.StringUtil
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.BuildConfig
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.database.RecipientDatabase
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobs.SyncSystemContactLinksJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.notifications.v2.ConversationId
@@ -45,9 +40,6 @@ object ContactDiscovery {
private val TAG = Log.tag(ContactDiscovery::class.java)
private const val MESSAGE_MIMETYPE = "vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.contact"
private const val CALL_MIMETYPE = "vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.call"
private const val CONTACT_TAG = "__TS"
private const val FULL_SYSTEM_CONTACT_SYNC_THRESHOLD = 3
@JvmStatic
@@ -154,8 +146,7 @@ object ContactDiscovery {
stopwatch.split("cds")
if (hasContactsPermissions(context)) {
addSystemContactLinks(context, result.registeredIds, removeSystemContactLinksIfMissing)
stopwatch.split("contact-links")
ApplicationDependencies.getJobManager().add(SyncSystemContactLinksJob())
val useFullSync = removeSystemContactLinksIfMissing && result.registeredIds.size > FULL_SYSTEM_CONTACT_SYNC_THRESHOLD
syncRecipientsWithSystemContacts(
@@ -215,70 +206,10 @@ object ContactDiscovery {
}
}
private fun buildContactLinkConfiguration(context: Context, account: Account): ContactLinkConfiguration {
return ContactLinkConfiguration(
account = account,
appName = context.getString(R.string.app_name),
messagePrompt = { e164 -> context.getString(R.string.ContactsDatabase_message_s, e164) },
callPrompt = { e164 -> context.getString(R.string.ContactsDatabase_signal_call_s, e164) },
e164Formatter = { number -> PhoneNumberFormatter.get(context).format(number) },
messageMimetype = MESSAGE_MIMETYPE,
callMimetype = CALL_MIMETYPE,
syncTag = CONTACT_TAG
)
}
private fun hasContactsPermissions(context: Context): Boolean {
return Permissions.hasAll(context, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)
}
/**
* Adds the "Message/Call $number with Signal" link to registered users in the system contacts.
* @param registeredIds A list of registered [RecipientId]s
* @param removeIfMissing If true, this will remove links from every currently-linked system contact that is *not* in the [registeredIds] list.
*/
private fun addSystemContactLinks(context: Context, registeredIds: Collection<RecipientId>, removeIfMissing: Boolean) {
if (!Permissions.hasAll(context, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)) {
Log.w(TAG, "[addSystemContactLinks] No contact permissions. Skipping.")
return
}
if (registeredIds.isEmpty()) {
Log.w(TAG, "[addSystemContactLinks] No registeredIds. Skipping.")
return
}
val stopwatch = Stopwatch("contact-links")
val account = SystemContactsRepository.getOrCreateSystemAccount(context, BuildConfig.APPLICATION_ID, context.getString(R.string.app_name))
if (account == null) {
Log.w(TAG, "[addSystemContactLinks] Failed to create an account!")
return
}
try {
val registeredE164s: Set<String> = SignalDatabase.recipients.getE164sForIds(registeredIds)
stopwatch.split("fetch-e164s")
SystemContactsRepository.removeDeletedRawContactsForAccount(context, account)
stopwatch.split("delete-stragglers")
SystemContactsRepository.addMessageAndCallLinksToContacts(
context = context,
config = buildContactLinkConfiguration(context, account),
targetE164s = registeredE164s,
removeIfMissing = removeIfMissing
)
stopwatch.split("add-links")
stopwatch.stop(TAG)
} catch (e: RemoteException) {
Log.w(TAG, "[addSystemContactLinks] Failed to add links to contacts.", e)
} catch (e: OperationApplicationException) {
Log.w(TAG, "[addSystemContactLinks] Failed to add links to contacts.", e)
}
}
/**
* Synchronizes info from the system contacts (name, avatar, etc)
*/

View File

@@ -172,7 +172,10 @@ object ContactDiscoveryRefreshV2 {
stopwatch.split("process-result")
val existingIds: Set<RecipientId> = SignalDatabase.recipients.getAllPossiblyRegisteredByE164(recipientE164s + rewrites.values)
stopwatch.split("get-ids")
val inactiveIds: Set<RecipientId> = (existingIds - registeredIds).removeRegisteredButUnlisted()
stopwatch.split("registered-but-unlisted")
SignalDatabase.recipients.bulkUpdatedRegisteredStatus(aciMap, inactiveIds)
stopwatch.split("update-registered")

View File

@@ -22,6 +22,7 @@ import org.signal.core.util.optionalInt
import org.signal.core.util.optionalLong
import org.signal.core.util.optionalString
import org.signal.core.util.or
import org.signal.core.util.readToSet
import org.signal.core.util.requireBlob
import org.signal.core.util.requireBoolean
import org.signal.core.util.requireInt
@@ -2201,12 +2202,12 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
}
fun bulkUpdatedRegisteredStatus(registered: Map<RecipientId, ServiceId?>, unregistered: Collection<RecipientId>) {
val db = writableDatabase
writableDatabase.withinTransaction { db ->
val registeredWithServiceId: Set<RecipientId> = getRegisteredWithServiceIds()
val needsMarkRegistered: Map<RecipientId, ServiceId?> = registered - registeredWithServiceId
db.beginTransaction()
try {
for ((recipientId, serviceId) in registered) {
val values = ContentValues(2).apply {
for ((recipientId, serviceId) in needsMarkRegistered) {
val values = ContentValues().apply {
put(REGISTERED, RegisteredState.REGISTERED.id)
put(UNREGISTERED_TIMESTAMP, 0)
if (serviceId != null) {
@@ -2236,10 +2237,6 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(id)
}
}
db.setTransactionSuccessful()
} finally {
db.endTransaction()
}
}
@@ -2907,6 +2904,17 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
return results
}
fun getRegisteredWithServiceIds(): Set<RecipientId> {
return readableDatabase
.select(ID)
.from(TABLE_NAME)
.where("$REGISTERED = ? and $HIDDEN = ? AND $SERVICE_ID NOT NULL", 1, 0)
.run()
.readToSet { cursor ->
RecipientId.from(cursor.requireLong(ID))
}
}
fun getSystemContacts(): List<RecipientId> {
val results: MutableList<RecipientId> = LinkedList()
@@ -2919,6 +2927,17 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
return results
}
fun getRegisteredE164s(): Set<String> {
return readableDatabase
.select(PHONE)
.from(TABLE_NAME)
.where("$REGISTERED = ? and $HIDDEN = ? AND $PHONE NOT NULL", 1, 0)
.run()
.readToSet { cursor ->
cursor.requireNonNullString(PHONE)
}
}
/**
* We no longer automatically generate a chat color. This method is used only
* in the case of a legacy migration and otherwise should not be called.

View File

@@ -175,6 +175,7 @@ public final class JobManagerFactories {
put(SendReadReceiptJob.KEY, new SendReadReceiptJob.Factory(application));
put(SendRetryReceiptJob.KEY, new SendRetryReceiptJob.Factory());
put(SendViewedReceiptJob.KEY, new SendViewedReceiptJob.Factory(application));
put(SyncSystemContactLinksJob.KEY, new SyncSystemContactLinksJob.Factory());
put(MultiDeviceStorySendSyncJob.KEY, new MultiDeviceStorySendSyncJob.Factory());
put(ServiceOutageDetectionJob.KEY, new ServiceOutageDetectionJob.Factory());
put(SmsReceiveJob.KEY, new SmsReceiveJob.Factory());

View File

@@ -0,0 +1,109 @@
package org.thoughtcrime.securesms.jobs
import android.Manifest
import android.accounts.Account
import android.content.Context
import android.content.OperationApplicationException
import android.os.RemoteException
import org.signal.contacts.ContactLinkConfiguration
import org.signal.contacts.SystemContactsRepository
import org.signal.core.util.Stopwatch
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.BuildConfig
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.jobmanager.Data
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
import java.lang.Exception
/**
* This job makes sure all of the contact "links" are up-to-date. The links are the actions you see when you look at a Signal user in your system contacts
* that let you send a message or start a call.
*/
class SyncSystemContactLinksJob private constructor(parameters: Parameters) : BaseJob(parameters) {
constructor() : this(
Parameters.Builder()
.setQueue("SyncSystemContactLinksJob")
.setMaxAttempts(1)
.setMaxInstancesForQueue(2)
.build()
)
override fun serialize(): Data = Data.EMPTY
override fun getFactoryKey() = KEY
override fun onFailure() = Unit
override fun onShouldRetry(e: Exception) = false
override fun onRun() {
if (!Permissions.hasAll(context, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)) {
Log.w(TAG, "No contact permissions. Skipping.")
return
}
val stopwatch = Stopwatch("contact-links")
val registeredE164s: Set<String> = SignalDatabase.recipients.getRegisteredE164s()
if (registeredE164s.isEmpty()) {
Log.w(TAG, "No registeredE164s. Skipping.")
return
}
stopwatch.split("fetch-e164s")
val account = SystemContactsRepository.getOrCreateSystemAccount(context, BuildConfig.APPLICATION_ID, context.getString(R.string.app_name))
if (account == null) {
Log.w(TAG, "Failed to create an account!")
return
}
try {
SystemContactsRepository.removeDeletedRawContactsForAccount(context, account)
stopwatch.split("delete-stragglers")
SystemContactsRepository.addMessageAndCallLinksToContacts(
context = context,
config = buildContactLinkConfiguration(context, account),
targetE164s = registeredE164s,
removeIfMissing = true
)
stopwatch.split("add-links")
stopwatch.stop(TAG)
} catch (e: RemoteException) {
Log.w(TAG, "[addSystemContactLinks] Failed to add links to contacts.", e)
} catch (e: OperationApplicationException) {
Log.w(TAG, "[addSystemContactLinks] Failed to add links to contacts.", e)
}
}
private fun buildContactLinkConfiguration(context: Context, account: Account): ContactLinkConfiguration {
return ContactLinkConfiguration(
account = account,
appName = context.getString(R.string.app_name),
messagePrompt = { e164 -> context.getString(R.string.ContactsDatabase_message_s, e164) },
callPrompt = { e164 -> context.getString(R.string.ContactsDatabase_signal_call_s, e164) },
e164Formatter = { number -> PhoneNumberFormatter.get(context).format(number) },
messageMimetype = MESSAGE_MIMETYPE,
callMimetype = CALL_MIMETYPE,
syncTag = CONTACT_TAG
)
}
class Factory : Job.Factory<SyncSystemContactLinksJob> {
override fun create(parameters: Parameters, data: Data) = SyncSystemContactLinksJob(parameters)
}
companion object {
private val TAG = Log.tag(SyncSystemContactLinksJob::class.java)
const val KEY = "SyncSystemContactLinksJob"
private const val MESSAGE_MIMETYPE = "vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.contact"
private const val CALL_MIMETYPE = "vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.call"
private const val CONTACT_TAG = "__TS"
}
}