mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-21 18:26:57 +00:00
Improve contact pull-to-refresh performance.
This commit is contained in:
committed by
Cody Henthorne
parent
2cfa685ae2
commit
4077dc829a
@@ -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)
|
||||
*/
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user