Limit the directory refresh in response to system contact changes.

Previously, we would do a full directory/CDS refresh in response to any
change in system contacts. That can be expensive.

This changes the behavior to look at how many new contacts there after
being notified of a contact change.

- If there aren't any, we just sync names and stuff.
- If we just have a few new contacts, we'll sync just those specifically.
- If we have a lot, we'll do a full sync.
fork-5.53.8
Greyson Parrelli 2021-04-07 16:29:00 -04:00 zatwierdzone przez Alan Evans
rodzic 1aa8e9753d
commit fcc49ae7b6
2 zmienionych plików z 133 dodań i 80 usunięć

Wyświetl plik

@ -7,16 +7,30 @@ import android.content.Context;
import android.content.SyncResult;
import android.os.Bundle;
import androidx.annotation.NonNull;
import com.annimon.stream.Stream;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.SetUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class ContactsSyncAdapter extends AbstractThreadedSyncAdapter {
private static final String TAG = Log.tag(ContactsSyncAdapter.class);
private static final int FULL_SYNC_THRESHOLD = 10;
public ContactsSyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
}
@ -27,12 +41,40 @@ public class ContactsSyncAdapter extends AbstractThreadedSyncAdapter {
{
Log.i(TAG, "onPerformSync(" + authority +")");
if (TextSecurePreferences.isPushRegistered(getContext())) {
Context context = getContext();
if (!TextSecurePreferences.isPushRegistered(context)) {
Log.i(TAG, "Not push registered. Just syncing contact info.");
DirectoryHelper.syncRecipientInfoWithSystemContacts(context);
return;
}
Set<String> allSystemNumbers = ContactAccessor.getInstance().getAllContactsWithNumbers(context);
Set<String> knownSystemNumbers = DatabaseFactory.getRecipientDatabase(context).getAllPhoneNumbers();
Set<String> unknownSystemNumbers = SetUtil.difference(allSystemNumbers, knownSystemNumbers);
if (unknownSystemNumbers.size() > FULL_SYNC_THRESHOLD) {
Log.i(TAG, "There are " + unknownSystemNumbers.size() + " unknown contacts. Doing a full sync.");
try {
DirectoryHelper.refreshDirectory(getContext(), true);
DirectoryHelper.refreshDirectory(context, true);
} catch (IOException e) {
Log.w(TAG, e);
}
} else if (unknownSystemNumbers.size() > 0) {
Log.i(TAG, "There are " + unknownSystemNumbers.size() + " unknown contacts. Doing an individual sync.");
List<Recipient> recipients = Stream.of(unknownSystemNumbers)
.filter(s -> s.startsWith("+"))
.map(s -> Recipient.external(getContext(), s))
.toList();
try {
DirectoryHelper.refreshDirectoryFor(context, recipients, true);
} catch (IOException e) {
Log.w(TAG, "Failed to refresh! Scheduling for later.", e);
ApplicationDependencies.getJobManager().add(new DirectoryRefreshJob(true));
}
} else {
Log.i(TAG, "No new contacts. Just syncing system contact data.");
DirectoryHelper.syncRecipientInfoWithSystemContacts(context);
}
}

Wyświetl plik

@ -209,6 +209,13 @@ public class DirectoryHelper {
return newRegisteredState;
}
/**
* Reads the system contacts and copies over any matching data (like names) int our local store.
*/
public static void syncRecipientInfoWithSystemContacts(@NonNull Context context) {
syncRecipientInfoWithSystemContacts(context, Collections.emptyMap());
}
@WorkerThread
private static void refreshNumbers(@NonNull Context context, @NonNull Set<String> databaseNumbers, @NonNull Set<String> systemNumbers, boolean notifyOfNewUsers) throws IOException {
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
@ -310,93 +317,97 @@ public class DirectoryHelper {
}
try {
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
ContactsDatabase contactsDatabase = DatabaseFactory.getContactsDatabase(context);
List<String> activeAddresses = Stream.of(activeIds)
.map(Recipient::resolved)
.filter(Recipient::hasE164)
.map(Recipient::requireE164)
.toList();
ContactsDatabase contactsDatabase = DatabaseFactory.getContactsDatabase(context);
List<String> activeAddresses = Stream.of(activeIds)
.map(Recipient::resolved)
.filter(Recipient::hasE164)
.map(Recipient::requireE164)
.toList();
contactsDatabase.removeDeletedRawContacts(account.getAccount());
contactsDatabase.setRegisteredUsers(account.getAccount(), activeAddresses, removeMissing);
BulkOperationsHandle handle = recipientDatabase.beginBulkSystemContactUpdate();
try (Cursor cursor = ContactAccessor.getInstance().getAllSystemContacts(context)) {
while (cursor != null && cursor.moveToNext()) {
String mimeType = getMimeType(cursor);
if (!isPhoneMimeType(mimeType)) {
Log.w(TAG, "Ignoring unwanted mime type: " + mimeType);
continue;
}
String lookupKey = getLookupKey(cursor);
ContactHolder contactHolder = new ContactHolder(lookupKey);
while (!cursor.isAfterLast() && getLookupKey(cursor).equals(lookupKey) && isPhoneMimeType(getMimeType(cursor))) {
String number = CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.Phone.NUMBER);
if (isValidContactNumber(number)) {
String formattedNumber = PhoneNumberFormatter.get(context).format(number);
String realNumber = Util.getFirstNonEmpty(rewrites.get(formattedNumber), formattedNumber);
PhoneNumberRecord.Builder builder = new PhoneNumberRecord.Builder();
builder.withRecipientId(Recipient.externalContact(context, realNumber).getId());
builder.withDisplayName(CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
builder.withContactPhotoUri(CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.Phone.PHOTO_URI));
builder.withContactLabel(CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.Phone.LABEL));
builder.withPhoneType(CursorUtil.requireInt(cursor, ContactsContract.CommonDataKinds.Phone.TYPE));
builder.withContactUri(ContactsContract.Contacts.getLookupUri(CursorUtil.requireLong(cursor, ContactsContract.CommonDataKinds.Phone._ID),
CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY)));
contactHolder.addPhoneNumberRecord(builder.build());
} else {
Log.w(TAG, "Skipping phone entry with invalid number");
}
cursor.moveToNext();
}
if (!cursor.isAfterLast() && getLookupKey(cursor).equals(lookupKey)) {
if (isStructuredNameMimeType(getMimeType(cursor))) {
StructuredNameRecord.Builder builder = new StructuredNameRecord.Builder();
builder.withGivenName(CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME));
builder.withFamilyName(CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME));
contactHolder.setStructuredNameRecord(builder.build());
} else {
Log.i(TAG, "Skipping invalid mimeType " + mimeType);
}
} else {
Log.i(TAG, "No structured name for user, rolling back cursor.");
cursor.moveToPrevious();
}
contactHolder.commit(handle);
}
} catch (IllegalStateException e) {
Log.w(TAG, "Hit an issue with the cursor while reading!", e);
} finally {
handle.finish();
}
if (NotificationChannels.supported()) {
try (RecipientDatabase.RecipientReader recipients = DatabaseFactory.getRecipientDatabase(context).getRecipientsWithNotificationChannels()) {
Recipient recipient;
while ((recipient = recipients.getNext()) != null) {
NotificationChannels.updateContactChannelName(context, recipient);
}
}
}
syncRecipientInfoWithSystemContacts(context, rewrites);
} catch (RemoteException | OperationApplicationException e) {
Log.w(TAG, "Failed to update contacts.", e);
}
}
private static void syncRecipientInfoWithSystemContacts(@NonNull Context context, @NonNull Map<String, String> rewrites) {
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
BulkOperationsHandle handle = recipientDatabase.beginBulkSystemContactUpdate();
try (Cursor cursor = ContactAccessor.getInstance().getAllSystemContacts(context)) {
while (cursor != null && cursor.moveToNext()) {
String mimeType = getMimeType(cursor);
if (!isPhoneMimeType(mimeType)) {
Log.w(TAG, "Ignoring unwanted mime type: " + mimeType);
continue;
}
String lookupKey = getLookupKey(cursor);
ContactHolder contactHolder = new ContactHolder(lookupKey);
while (!cursor.isAfterLast() && getLookupKey(cursor).equals(lookupKey) && isPhoneMimeType(getMimeType(cursor))) {
String number = CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.Phone.NUMBER);
if (isValidContactNumber(number)) {
String formattedNumber = PhoneNumberFormatter.get(context).format(number);
String realNumber = Util.getFirstNonEmpty(rewrites.get(formattedNumber), formattedNumber);
PhoneNumberRecord.Builder builder = new PhoneNumberRecord.Builder();
builder.withRecipientId(Recipient.externalContact(context, realNumber).getId());
builder.withDisplayName(CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
builder.withContactPhotoUri(CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.Phone.PHOTO_URI));
builder.withContactLabel(CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.Phone.LABEL));
builder.withPhoneType(CursorUtil.requireInt(cursor, ContactsContract.CommonDataKinds.Phone.TYPE));
builder.withContactUri(ContactsContract.Contacts.getLookupUri(CursorUtil.requireLong(cursor, ContactsContract.CommonDataKinds.Phone._ID),
CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY)));
contactHolder.addPhoneNumberRecord(builder.build());
} else {
Log.w(TAG, "Skipping phone entry with invalid number");
}
cursor.moveToNext();
}
if (!cursor.isAfterLast() && getLookupKey(cursor).equals(lookupKey)) {
if (isStructuredNameMimeType(getMimeType(cursor))) {
StructuredNameRecord.Builder builder = new StructuredNameRecord.Builder();
builder.withGivenName(CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME));
builder.withFamilyName(CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME));
contactHolder.setStructuredNameRecord(builder.build());
} else {
Log.i(TAG, "Skipping invalid mimeType " + mimeType);
}
} else {
Log.i(TAG, "No structured name for user, rolling back cursor.");
cursor.moveToPrevious();
}
contactHolder.commit(handle);
}
} catch (IllegalStateException e) {
Log.w(TAG, "Hit an issue with the cursor while reading!", e);
} finally {
handle.finish();
}
if (NotificationChannels.supported()) {
try (RecipientDatabase.RecipientReader recipients = DatabaseFactory.getRecipientDatabase(context).getRecipientsWithNotificationChannels()) {
Recipient recipient;
while ((recipient = recipients.getNext()) != null) {
NotificationChannels.updateContactChannelName(context, recipient);
}
}
}
}
private static boolean isPhoneMimeType(@NonNull String mimeType) {
return ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE.equals(mimeType);
}