|
|
@@ -1,550 +0,0 @@
|
|
|
-package ch.threema.app.routines;
|
|
|
-
|
|
|
-import android.Manifest;
|
|
|
-import android.content.ContentProviderOperation;
|
|
|
-import android.content.ContentResolver;
|
|
|
-import android.content.Context;
|
|
|
-import android.database.Cursor;
|
|
|
-import android.provider.ContactsContract;
|
|
|
-
|
|
|
-import androidx.annotation.NonNull;
|
|
|
-import androidx.annotation.OptIn;
|
|
|
-import androidx.annotation.RequiresPermission;
|
|
|
-
|
|
|
-import com.google.common.collect.ListMultimap;
|
|
|
-
|
|
|
-import org.slf4j.Logger;
|
|
|
-
|
|
|
-import java.math.BigInteger;
|
|
|
-import java.time.Instant;
|
|
|
-import java.time.temporal.ChronoUnit;
|
|
|
-import java.util.ArrayList;
|
|
|
-import java.util.HashMap;
|
|
|
-import java.util.List;
|
|
|
-import java.util.Map;
|
|
|
-import java.util.Set;
|
|
|
-import java.util.concurrent.CountDownLatch;
|
|
|
-
|
|
|
-import ch.threema.app.apptaskexecutor.AppTaskExecutor;
|
|
|
-import ch.threema.app.preference.service.SynchronizedSettingsService;
|
|
|
-import ch.threema.app.androidcontactsync.usecases.UpdateContactNameUseCase;
|
|
|
-import ch.threema.data.datatypes.AndroidContactLookupInfo;
|
|
|
-import ch.threema.app.asynctasks.AddContactBackgroundTask;
|
|
|
-import ch.threema.app.services.BlockedIdentitiesService;
|
|
|
-import ch.threema.app.services.ContactService;
|
|
|
-import ch.threema.app.services.DeviceService;
|
|
|
-import ch.threema.app.services.ExcludedSyncIdentitiesService;
|
|
|
-import ch.threema.app.services.LocaleService;
|
|
|
-import ch.threema.app.preference.service.PreferenceService;
|
|
|
-import ch.threema.app.services.UserService;
|
|
|
-import ch.threema.app.stores.MatchTokenStore;
|
|
|
-import ch.threema.app.utils.AndroidContactUtil;
|
|
|
-import ch.threema.app.utils.ConfigUtils;
|
|
|
-import ch.threema.app.utils.ContactUtil;
|
|
|
-import ch.threema.base.ThreemaException;
|
|
|
-
|
|
|
-import static ch.threema.base.utils.LoggingKt.getThreemaLogger;
|
|
|
-import static ch.threema.common.JavaCompat.isNullOrEmpty;
|
|
|
-
|
|
|
-import ch.threema.data.datatypes.AvailabilityStatus;
|
|
|
-import ch.threema.data.datatypes.ConversationVisibility;
|
|
|
-import ch.threema.data.models.ContactModel;
|
|
|
-import ch.threema.data.models.ContactModelData;
|
|
|
-import ch.threema.data.repositories.ContactModelRepository;
|
|
|
-import ch.threema.domain.models.AcquaintanceLevel;
|
|
|
-import ch.threema.domain.models.ContactSyncState;
|
|
|
-import ch.threema.domain.models.IdentityState;
|
|
|
-import ch.threema.domain.models.IdentityType;
|
|
|
-import ch.threema.domain.models.ReadReceiptPolicy;
|
|
|
-import ch.threema.domain.models.TypingIndicatorPolicy;
|
|
|
-import ch.threema.domain.models.VerificationLevel;
|
|
|
-import ch.threema.domain.models.WorkVerificationLevel;
|
|
|
-import ch.threema.domain.protocol.api.APIConnector;
|
|
|
-import ch.threema.domain.stores.IdentityStore;
|
|
|
-import ch.threema.data.datatypes.IdColor;
|
|
|
-import kotlin.Unit;
|
|
|
-import kotlinx.coroutines.DelicateCoroutinesApi;
|
|
|
-
|
|
|
-public class SynchronizeContactsRoutine implements Runnable {
|
|
|
- private static final Logger logger = getThreemaLogger("SynchronizeContactsRoutine");
|
|
|
-
|
|
|
- private final UserService userService;
|
|
|
- private final Context context;
|
|
|
- private final APIConnector apiConnector;
|
|
|
- private final ContactService contactService;
|
|
|
- private final ContactModelRepository contactModelRepository;
|
|
|
- private final LocaleService localeService;
|
|
|
- private final ContentResolver contentResolver;
|
|
|
- @NonNull
|
|
|
- private final ExcludedSyncIdentitiesService excludedSyncIdentitiesService;
|
|
|
- private final DeviceService deviceService;
|
|
|
- private final PreferenceService preferenceService;
|
|
|
- private final SynchronizedSettingsService synchronizedSettingsService;
|
|
|
- private final IdentityStore identityStore;
|
|
|
- private final BlockedIdentitiesService blockedIdentitiesService;
|
|
|
- @NonNull
|
|
|
- private final AppTaskExecutor appTaskExecutor;
|
|
|
- @NonNull
|
|
|
- private final UpdateContactNameUseCase updateContactNameUseCase;
|
|
|
-
|
|
|
- private OnStatusUpdate onStatusUpdate;
|
|
|
- private final List<OnFinished> onFinished = new ArrayList<>();
|
|
|
-
|
|
|
- @NonNull
|
|
|
- private final Set<String> processingIdentities;
|
|
|
- private boolean abort = false;
|
|
|
- private boolean running = false;
|
|
|
-
|
|
|
- public interface OnStatusUpdate {
|
|
|
- void error(final Exception x);
|
|
|
- }
|
|
|
-
|
|
|
- public interface OnFinished {
|
|
|
- void finished(boolean success, long modifiedAccounts, List<ContactModel> createdContacts, long deletedAccounts);
|
|
|
- }
|
|
|
-
|
|
|
- public SynchronizeContactsRoutine(
|
|
|
- Context context,
|
|
|
- APIConnector apiConnector,
|
|
|
- ContactService contactService,
|
|
|
- @NonNull ContactModelRepository contactModelRepository,
|
|
|
- UserService userService,
|
|
|
- LocaleService localeService,
|
|
|
- ContentResolver contentResolver,
|
|
|
- @NonNull ExcludedSyncIdentitiesService excludedSyncIdentitiesService,
|
|
|
- DeviceService deviceService,
|
|
|
- PreferenceService preferenceService,
|
|
|
- @NonNull SynchronizedSettingsService synchronizedSettingsService,
|
|
|
- IdentityStore identityStore,
|
|
|
- BlockedIdentitiesService blockedIdentitiesService,
|
|
|
- @NonNull AppTaskExecutor appTaskExecutor,
|
|
|
- @NonNull UpdateContactNameUseCase updateContactNameUseCase,
|
|
|
- @NonNull Set<String> processingIdentities
|
|
|
- ) {
|
|
|
- this.context = context;
|
|
|
- this.apiConnector = apiConnector;
|
|
|
- this.userService = userService;
|
|
|
- this.contactService = contactService;
|
|
|
- this.contactModelRepository = contactModelRepository;
|
|
|
- this.localeService = localeService;
|
|
|
- this.contentResolver = contentResolver;
|
|
|
- this.excludedSyncIdentitiesService = excludedSyncIdentitiesService;
|
|
|
- this.deviceService = deviceService;
|
|
|
- this.preferenceService = preferenceService;
|
|
|
- this.synchronizedSettingsService = synchronizedSettingsService;
|
|
|
- this.identityStore = identityStore;
|
|
|
- this.blockedIdentitiesService = blockedIdentitiesService;
|
|
|
- this.appTaskExecutor = appTaskExecutor;
|
|
|
- this.updateContactNameUseCase = updateContactNameUseCase;
|
|
|
- this.processingIdentities = processingIdentities;
|
|
|
- }
|
|
|
-
|
|
|
- public void abort() {
|
|
|
- this.abort = true;
|
|
|
- }
|
|
|
-
|
|
|
- public boolean running() {
|
|
|
- return this.running;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Check whether all contacts are being synchronized. If it is not a full sync, then only some
|
|
|
- * specific identities are updated.
|
|
|
- */
|
|
|
- public boolean isFullSync() {
|
|
|
- return this.processingIdentities.isEmpty();
|
|
|
- }
|
|
|
-
|
|
|
- @OptIn(markerClass = DelicateCoroutinesApi.class)
|
|
|
- @Override
|
|
|
- @RequiresPermission(allOf = {Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
|
|
|
- public void run() {
|
|
|
- logger.info("SynchronizeContacts run started.");
|
|
|
-
|
|
|
- if (!ConfigUtils.isPermissionGranted(context, Manifest.permission.WRITE_CONTACTS)) {
|
|
|
- logger.info("No contacts permission. Aborting.");
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- this.running = true;
|
|
|
-
|
|
|
- boolean success = false;
|
|
|
-
|
|
|
- long deletedCount = 0;
|
|
|
- long modifiedRawContactCount = 0;
|
|
|
- long newRawContactCount = 0;
|
|
|
- // The list of contacts that have been added in this run because they are found by phone or email.
|
|
|
- List<ContactModel> insertedContacts = new ArrayList<>();
|
|
|
-
|
|
|
- try {
|
|
|
- if (!synchronizedSettingsService.isSyncContacts()) {
|
|
|
- throw new ThreemaException("Sync is disabled in preferences, not allowed to call synchronizecontacts routine");
|
|
|
- }
|
|
|
-
|
|
|
- if (this.deviceService != null && !this.deviceService.isOnline()) {
|
|
|
- throw new ThreemaException("No connection");
|
|
|
- }
|
|
|
-
|
|
|
- //read emails
|
|
|
- final Map<String, ContactMatchKeyEmail> emails = this.readEmails();
|
|
|
- final int emailsHash = emails.keySet().hashCode();
|
|
|
-
|
|
|
- //read phone numbers
|
|
|
- final Map<String, ContactMatchKeyPhone> phoneNumbers = this.readPhoneNumbers();
|
|
|
- final int phoneNumbersHash = phoneNumbers.keySet().hashCode();
|
|
|
-
|
|
|
- Instant timeOfLastContactSync = preferenceService.getTimeOfLastContactSync();
|
|
|
- if (preferenceService.getEmailSyncHashCode() == emailsHash
|
|
|
- && preferenceService.getPhoneNumberSyncHashCode() == phoneNumbersHash
|
|
|
- && (timeOfLastContactSync != null && timeOfLastContactSync.isAfter(Instant.now().minus(23, ChronoUnit.DAYS)))) {
|
|
|
- logger.info("System contacts are unchanged or grace time not yet reached. Not syncing.");
|
|
|
- success = true;
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- preferenceService.setEmailSyncHashCode(emailsHash);
|
|
|
- preferenceService.setPhoneNumberSyncHashCode(phoneNumbersHash);
|
|
|
- preferenceService.setTimeOfLastContactSync(Instant.now());
|
|
|
-
|
|
|
- logger.info("Attempting to sync contacts: {} emails, {} phone numbers", emails.size(), phoneNumbers.size());
|
|
|
-
|
|
|
- //send hashes to server and get result
|
|
|
- MatchTokenStore matchTokenStore = new MatchTokenStore(this.preferenceService);
|
|
|
- Map<String, APIConnector.MatchIdentityResult> matchIdentityResults = this.apiConnector.matchIdentities(
|
|
|
- emails, phoneNumbers, this.localeService.getCountryIsoCode(), false, identityStore, matchTokenStore);
|
|
|
-
|
|
|
- // remove own ID
|
|
|
- if (userService != null) {
|
|
|
- matchIdentityResults.remove(userService.getIdentity());
|
|
|
- }
|
|
|
-
|
|
|
- final List<String> preSynchronizedIdentities = new ArrayList<>();
|
|
|
- if (this.isFullSync()) {
|
|
|
- List<String> synchronizedIdentities = this.contactService.getSynchronizedIdentities();
|
|
|
- if (synchronizedIdentities != null) {
|
|
|
- preSynchronizedIdentities.addAll(synchronizedIdentities);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- ListMultimap<String, AndroidContactUtil.RawContactInfo> existingRawContacts = AndroidContactUtil.getInstance().getAllThreemaRawContacts();
|
|
|
- if (existingRawContacts == null) {
|
|
|
- throw new ThreemaException("No permission or no account");
|
|
|
- }
|
|
|
- logger.info("Number of existing raw contacts: {}", existingRawContacts.size());
|
|
|
-
|
|
|
- logger.info("Number of IDs matching phone or email: {}", matchIdentityResults.size());
|
|
|
-
|
|
|
- ArrayList<ContentProviderOperation> contentProviderOperations = new ArrayList<>();
|
|
|
- for (Map.Entry<String, APIConnector.MatchIdentityResult> matchIdentityResultEntry : matchIdentityResults.entrySet()) {
|
|
|
- String identity = matchIdentityResultEntry.getKey();
|
|
|
- APIConnector.MatchIdentityResult result = matchIdentityResultEntry.getValue();
|
|
|
-
|
|
|
- if (this.abort) {
|
|
|
- //abort!
|
|
|
- for (OnFinished f : this.onFinished) {
|
|
|
- f.finished(false, modifiedRawContactCount, insertedContacts, deletedCount);
|
|
|
- }
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // Do not sync contacts on exclude list
|
|
|
- if (this.excludedSyncIdentitiesService.isExcluded(identity)) {
|
|
|
- logger.info("Identity {} is on exclude list", identity);
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- // If it is not a full sync and the current identity is not one of the identities to
|
|
|
- // be updated in this run, we skip processing this identity.
|
|
|
- if (!isFullSync() && !this.processingIdentities.contains(identity)) {
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- if (this.isFullSync()) {
|
|
|
- // remove if list contains this key
|
|
|
- preSynchronizedIdentities.remove(identity);
|
|
|
- }
|
|
|
-
|
|
|
- final ContactMatchKeyEmail matchKeyEmail = (ContactMatchKeyEmail) result.refObjectEmail;
|
|
|
- final ContactMatchKeyPhone matchKeyPhone = (ContactMatchKeyPhone) result.refObjectMobileNo;
|
|
|
-
|
|
|
- long contactId;
|
|
|
- String lookupKey;
|
|
|
- if (matchKeyEmail != null) {
|
|
|
- contactId = matchKeyEmail.contactId;
|
|
|
- lookupKey = matchKeyEmail.lookupKey;
|
|
|
- } else {
|
|
|
- contactId = matchKeyPhone.contactId;
|
|
|
- lookupKey = matchKeyPhone.lookupKey;
|
|
|
- }
|
|
|
-
|
|
|
- //try to get the contact
|
|
|
- ContactModel contact = contactModelRepository.getByIdentity(identity);
|
|
|
- ContactModelData data = contact != null ? contact.getData() : null;
|
|
|
- //contact does not exist, create a new one
|
|
|
- if (contact == null || data == null) {
|
|
|
- ContactModelData contactModelData = ContactModelData.javaCreate(
|
|
|
- identity,
|
|
|
- result.publicKey,
|
|
|
- Instant.now(),
|
|
|
- null,
|
|
|
- "",
|
|
|
- "",
|
|
|
- null,
|
|
|
- IdColor.ofIdentity(identity),
|
|
|
- VerificationLevel.SERVER_VERIFIED,
|
|
|
- WorkVerificationLevel.NONE,
|
|
|
- IdentityType.REGULAR, // TODO(ANDR-3044): Fetch identity type
|
|
|
- AcquaintanceLevel.DIRECT,
|
|
|
- IdentityState.ACTIVE, // TODO(ANDR-3044): Fetch identity state
|
|
|
- BigInteger.valueOf(0), // TODO(ANDR-3044): Fetch feature mask
|
|
|
- ContactSyncState.IMPORTED,
|
|
|
- ReadReceiptPolicy.DEFAULT,
|
|
|
- TypingIndicatorPolicy.DEFAULT,
|
|
|
- ConversationVisibility.NORMAL,
|
|
|
- null,
|
|
|
- null,
|
|
|
- false,
|
|
|
- null,
|
|
|
- null,
|
|
|
- null,
|
|
|
- null,
|
|
|
- AvailabilityStatus.None.INSTANCE,
|
|
|
- null
|
|
|
- );
|
|
|
- contact = new AddContactBackgroundTask(contactModelData, contactModelRepository)
|
|
|
- .runSynchronously();
|
|
|
-
|
|
|
- if (contact == null) {
|
|
|
- logger.error("Could not create contact with identity {}", identity);
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- data = contact.getData();
|
|
|
- if (data == null) {
|
|
|
- logger.error("Contact data is null");
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- insertedContacts.add(contact);
|
|
|
-
|
|
|
- logger.info("Inserting new Threema contact {}", identity);
|
|
|
- }
|
|
|
- contact.setAndroidContactLookupKey(
|
|
|
- new AndroidContactLookupInfo(lookupKey, contactId)
|
|
|
- );
|
|
|
-
|
|
|
- boolean createNewRawContact;
|
|
|
-
|
|
|
- CountDownLatch latch = new CountDownLatch(1);
|
|
|
- final ContactModel finalContactModel = contact;
|
|
|
- appTaskExecutor.runInAppTask((continuation -> {
|
|
|
- // Note that continuation can only be used once to call a suspend function!
|
|
|
- updateContactNameUseCase.call(Set.of(finalContactModel), continuation);
|
|
|
- return Unit.INSTANCE;
|
|
|
- })).invokeOnCompletion(throwable -> {
|
|
|
- latch.countDown();
|
|
|
- return Unit.INSTANCE;
|
|
|
- });
|
|
|
- latch.await();
|
|
|
-
|
|
|
- AndroidContactUtil.getInstance().updateAvatarByAndroidContact(contact, context);
|
|
|
-
|
|
|
- contact.setAcquaintanceLevelFromLocal(AcquaintanceLevel.DIRECT);
|
|
|
- if (data.verificationLevel == VerificationLevel.UNVERIFIED) {
|
|
|
- contact.setVerificationLevelFromLocal(VerificationLevel.SERVER_VERIFIED);
|
|
|
- }
|
|
|
-
|
|
|
- List<AndroidContactUtil.RawContactInfo> rawContactInfos = existingRawContacts.get(contact.getIdentity());
|
|
|
-
|
|
|
- if (rawContactInfos.isEmpty()) {
|
|
|
- // raw contact does not exist yet, create it
|
|
|
- createNewRawContact = true;
|
|
|
- newRawContactCount++;
|
|
|
- } else {
|
|
|
- // a raw contact exists - check if it points to the correct parent
|
|
|
- createNewRawContact = true;
|
|
|
- for (AndroidContactUtil.RawContactInfo rawContactInfo : rawContactInfos) {
|
|
|
- if (rawContactInfo.contactId > 0L && rawContactInfo.contactId == contactId) {
|
|
|
- // all good - no change necessary
|
|
|
- createNewRawContact = false;
|
|
|
- existingRawContacts.remove(contact.getIdentity(), rawContactInfo);
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- if (createNewRawContact) {
|
|
|
- modifiedRawContactCount++;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (createNewRawContact) {
|
|
|
- boolean supportsVoiceCalls = ContactUtil.canReceiveVoipMessages(contact.getIdentity(), this.blockedIdentitiesService)
|
|
|
- && ConfigUtils.isCallsEnabled();
|
|
|
-
|
|
|
- // create a raw contact for our stuff and aggregate it
|
|
|
- AndroidContactUtil.getInstance().createThreemaRawContact(
|
|
|
- contentProviderOperations,
|
|
|
- matchKeyEmail != null ?
|
|
|
- matchKeyEmail.rawContactId :
|
|
|
- matchKeyPhone.rawContactId,
|
|
|
- contact.getIdentity(),
|
|
|
- supportsVoiceCalls);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (!contentProviderOperations.isEmpty()) {
|
|
|
- try {
|
|
|
- ConfigUtils.applyToContentResolverInBatches(
|
|
|
- ContactsContract.AUTHORITY,
|
|
|
- contentProviderOperations);
|
|
|
- } catch (Exception e) {
|
|
|
- logger.error("Error during raw contact creation! ", e);
|
|
|
- }
|
|
|
- contentProviderOperations.clear();
|
|
|
- }
|
|
|
-
|
|
|
- if (this.isFullSync()) {
|
|
|
- // delete remaining / stray raw contacts
|
|
|
- if (!existingRawContacts.isEmpty()) {
|
|
|
- AndroidContactUtil.getInstance().deleteThreemaRawContacts(existingRawContacts);
|
|
|
- logger.info("Deleted {} stray raw contacts", existingRawContacts.size());
|
|
|
- }
|
|
|
-
|
|
|
- if (!preSynchronizedIdentities.isEmpty()) {
|
|
|
- logger.info("Found {} synchronized contacts that are no longer synchronized", preSynchronizedIdentities.size());
|
|
|
-
|
|
|
- for (String identity : preSynchronizedIdentities) {
|
|
|
- ContactModel contactModel = contactModelRepository.getByIdentity(identity);
|
|
|
- if (contactModel != null) {
|
|
|
- contactModel.removeAndroidContactLink();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- success = true;
|
|
|
- } catch (final Exception x) {
|
|
|
- logger.error("Exception", x);
|
|
|
- if (this.onStatusUpdate != null) {
|
|
|
- this.onStatusUpdate.error(x);
|
|
|
- }
|
|
|
- } finally {
|
|
|
- logger.info("Finished [success = {}, inserted = {}, deleted = {}, modifiedRawContacts = {}, newRawContacts = {}] fullSync = {}", success, insertedContacts.size(), deletedCount, modifiedRawContactCount, newRawContactCount, this.isFullSync());
|
|
|
- for (OnFinished f : this.onFinished) {
|
|
|
- f.finished(success, modifiedRawContactCount, insertedContacts, deletedCount);
|
|
|
- }
|
|
|
- this.running = false;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public SynchronizeContactsRoutine setOnStatusUpdate(OnStatusUpdate onStatusUpdate) {
|
|
|
- this.onStatusUpdate = onStatusUpdate;
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- public SynchronizeContactsRoutine addOnFinished(OnFinished onFinished) {
|
|
|
- this.onFinished.add(onFinished);
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- private Map<String, ContactMatchKeyPhone> readPhoneNumbers() {
|
|
|
- Map<String, ContactMatchKeyPhone> phoneNumbers = new HashMap<>();
|
|
|
- String selection;
|
|
|
-
|
|
|
- selection = ContactsContract.Contacts.HAS_PHONE_NUMBER + " > 0 AND " + ContactsContract.CommonDataKinds.Phone.IN_DEFAULT_DIRECTORY + " = 1";
|
|
|
-
|
|
|
- try (Cursor phonesCursor = this.contentResolver.query(
|
|
|
- ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
|
|
|
- new String[]{
|
|
|
- ContactsContract.CommonDataKinds.Phone.RAW_CONTACT_ID,
|
|
|
- ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
|
|
|
- ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY,
|
|
|
- ContactsContract.CommonDataKinds.Phone.NUMBER,
|
|
|
- },
|
|
|
- selection,
|
|
|
- null,
|
|
|
- null)) {
|
|
|
-
|
|
|
- if (phonesCursor != null && phonesCursor.getCount() > 0) {
|
|
|
- final int rawContactIdIndex = phonesCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.RAW_CONTACT_ID);
|
|
|
- final int idColumnIndex = phonesCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.CONTACT_ID);
|
|
|
- final int lookupKeyColumnIndex = phonesCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY);
|
|
|
- final int phoneNumberIndex = phonesCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
|
|
|
-
|
|
|
- while (phonesCursor.moveToNext()) {
|
|
|
- long rawContactId = phonesCursor.getLong(rawContactIdIndex);
|
|
|
- long contactId = phonesCursor.getLong(idColumnIndex);
|
|
|
- String lookupKey = phonesCursor.getString(lookupKeyColumnIndex);
|
|
|
- String phoneNumber = phonesCursor.getString(phoneNumberIndex);
|
|
|
-
|
|
|
- if (rawContactId > 0L && contactId > 0L && lookupKey != null && !isNullOrEmpty(phoneNumber)) {
|
|
|
- ContactMatchKeyPhone matchKey = new ContactMatchKeyPhone();
|
|
|
- matchKey.contactId = contactId;
|
|
|
- matchKey.lookupKey = lookupKey;
|
|
|
- matchKey.rawContactId = rawContactId;
|
|
|
- matchKey.phoneNumber = phoneNumber;
|
|
|
- phoneNumbers.put(phoneNumber, matchKey);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return phoneNumbers;
|
|
|
- }
|
|
|
-
|
|
|
- private Map<String, ContactMatchKeyEmail> readEmails() {
|
|
|
- Map<String, ContactMatchKeyEmail> emails = new HashMap<>();
|
|
|
-
|
|
|
- String selection = ContactsContract.CommonDataKinds.Email.IN_DEFAULT_DIRECTORY + " = 1";
|
|
|
-
|
|
|
- try (Cursor emailsCursor = this.contentResolver.query(
|
|
|
- ContactsContract.CommonDataKinds.Email.CONTENT_URI,
|
|
|
- new String[]{
|
|
|
- ContactsContract.CommonDataKinds.Email.RAW_CONTACT_ID,
|
|
|
- ContactsContract.CommonDataKinds.Email.CONTACT_ID,
|
|
|
- ContactsContract.CommonDataKinds.Email.LOOKUP_KEY,
|
|
|
- ContactsContract.CommonDataKinds.Email.DATA
|
|
|
- },
|
|
|
- selection,
|
|
|
- null,
|
|
|
- null)) {
|
|
|
-
|
|
|
- if (emailsCursor != null && emailsCursor.getCount() > 0) {
|
|
|
- final int rawContactIdIndex = emailsCursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.RAW_CONTACT_ID);
|
|
|
- final int idColumnIndex = emailsCursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.CONTACT_ID);
|
|
|
- final int lookupKeyColumnIndex = emailsCursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.LOOKUP_KEY);
|
|
|
- final int emailIndex = emailsCursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA);
|
|
|
-
|
|
|
- while (emailsCursor.moveToNext()) {
|
|
|
- long rawContactId = emailsCursor.getLong(rawContactIdIndex);
|
|
|
- long contactId = emailsCursor.getLong(idColumnIndex);
|
|
|
- String lookupKey = emailsCursor.getString(lookupKeyColumnIndex);
|
|
|
- String email = emailsCursor.getString(emailIndex);
|
|
|
-
|
|
|
- if (rawContactId > 0L && contactId > 0L && lookupKey != null && !isNullOrEmpty(email)) {
|
|
|
- ContactMatchKeyEmail matchKey = new ContactMatchKeyEmail();
|
|
|
- matchKey.contactId = contactId;
|
|
|
- matchKey.lookupKey = lookupKey;
|
|
|
- matchKey.rawContactId = rawContactId;
|
|
|
- matchKey.email = email;
|
|
|
- emails.put(email, matchKey);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return emails;
|
|
|
- }
|
|
|
-
|
|
|
- private static class ContactMatchKey {
|
|
|
- long contactId;
|
|
|
- String lookupKey;
|
|
|
- long rawContactId;
|
|
|
- }
|
|
|
-
|
|
|
- private static class ContactMatchKeyEmail extends ContactMatchKey {
|
|
|
- String email;
|
|
|
- }
|
|
|
-
|
|
|
- private static class ContactMatchKeyPhone extends ContactMatchKey {
|
|
|
- String phoneNumber;
|
|
|
- }
|
|
|
-}
|