kopia lustrzana https://github.com/ryukoposting/Signal-Android
Migrate contact interactions to SystemContactsRepository.
rodzic
db309b7930
commit
c2627dda8d
|
@ -27,8 +27,8 @@ import java.util.Map;
|
|||
* Repository for all contacts. Allows you to filter them via queries.
|
||||
*
|
||||
* Currently this is implemented to return cursors. This is to ease the migration between this class
|
||||
* and the previous way we'd query contacts: {@link ContactsDatabase}. It's much easier in the
|
||||
* short-term to mock the cursor interface rather than try to switch everything over to models.
|
||||
* and the previous way we'd query contacts. It's much easier in the short-term to mock the cursor
|
||||
* interface rather than try to switch everything over to models.
|
||||
*/
|
||||
public class ContactRepository {
|
||||
|
||||
|
|
|
@ -1,506 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.contacts;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.OperationApplicationException;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.BaseColumns;
|
||||
import android.provider.ContactsContract;
|
||||
import android.provider.ContactsContract.RawContacts;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Database to supply all types of contacts that TextSecure needs to know about
|
||||
*
|
||||
* @author Jake McGinty
|
||||
*/
|
||||
public class ContactsDatabase {
|
||||
|
||||
private static final String TAG = Log.tag(ContactsDatabase.class);
|
||||
private static final String CONTACT_MIMETYPE = "vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.contact";
|
||||
private static final String CALL_MIMETYPE = "vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.call";
|
||||
private static final String SYNC = "__TS";
|
||||
|
||||
private final Context context;
|
||||
|
||||
public ContactsDatabase(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public synchronized void removeDeletedRawContacts(@NonNull Account account) {
|
||||
Uri currentContactsUri = RawContacts.CONTENT_URI.buildUpon()
|
||||
.appendQueryParameter(RawContacts.ACCOUNT_NAME, account.name)
|
||||
.appendQueryParameter(RawContacts.ACCOUNT_TYPE, account.type)
|
||||
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
|
||||
.build();
|
||||
|
||||
String[] projection = new String[] {BaseColumns._ID, RawContacts.SYNC1};
|
||||
|
||||
try (Cursor cursor = context.getContentResolver().query(currentContactsUri, projection, RawContacts.DELETED + " = ?", new String[] {"1"}, null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
long rawContactId = cursor.getLong(0);
|
||||
Log.i(TAG, "Deleting raw contact: " + cursor.getString(1) + ", " + rawContactId);
|
||||
|
||||
context.getContentResolver().delete(currentContactsUri, RawContacts._ID + " = ?", new String[] {String.valueOf(rawContactId)});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void setRegisteredUsers(@NonNull Account account,
|
||||
@NonNull List<String> registeredAddressList,
|
||||
boolean remove)
|
||||
throws RemoteException, OperationApplicationException
|
||||
{
|
||||
Set<String> registeredAddressSet = new HashSet<>(registeredAddressList);
|
||||
ArrayList<ContentProviderOperation> operations = new ArrayList<>();
|
||||
Map<String, SignalContact> currentContacts = getSignalRawContacts(account);
|
||||
List<List<String>> registeredChunks = Util.chunk(registeredAddressList, 50);
|
||||
|
||||
for (List<String> registeredChunk : registeredChunks) {
|
||||
for (String registeredAddress : registeredChunk) {
|
||||
if (!currentContacts.containsKey(registeredAddress)) {
|
||||
Optional<SystemContactInfo> systemContactInfo = getSystemContactInfo(registeredAddress);
|
||||
|
||||
if (systemContactInfo.isPresent()) {
|
||||
Log.i(TAG, "Adding number: " + registeredAddress);
|
||||
addTextSecureRawContact(operations, account, systemContactInfo.get().number,
|
||||
systemContactInfo.get().name, systemContactInfo.get().id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!operations.isEmpty()) {
|
||||
context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations);
|
||||
operations.clear();
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, SignalContact> currentContactEntry : currentContacts.entrySet()) {
|
||||
if (!registeredAddressSet.contains(currentContactEntry.getKey())) {
|
||||
if (remove) {
|
||||
Log.i(TAG, "Removing number: " + currentContactEntry.getKey());
|
||||
removeTextSecureRawContact(operations, account, currentContactEntry.getValue().getId());
|
||||
}
|
||||
} else if (!currentContactEntry.getValue().isVoiceSupported()) {
|
||||
Log.i(TAG, "Adding voice support: " + currentContactEntry.getKey());
|
||||
addContactVoiceSupport(operations, currentContactEntry.getKey(), currentContactEntry.getValue().getId());
|
||||
} else if (!Util.isStringEquals(currentContactEntry.getValue().getRawDisplayName(),
|
||||
currentContactEntry.getValue().getAggregateDisplayName()))
|
||||
{
|
||||
Log.i(TAG, "Updating display name: " + currentContactEntry.getKey());
|
||||
updateDisplayName(operations, currentContactEntry.getValue().getAggregateDisplayName(), currentContactEntry.getValue().getId(), currentContactEntry.getValue().getDisplayNameSource());
|
||||
}
|
||||
}
|
||||
|
||||
if (!operations.isEmpty()) {
|
||||
applyOperationsInBatches(context.getContentResolver(), ContactsContract.AUTHORITY, operations, 50);
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable Cursor getNameDetails(long contactId) {
|
||||
String[] projection = new String[] { ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,
|
||||
ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME,
|
||||
ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME,
|
||||
ContactsContract.CommonDataKinds.StructuredName.PREFIX,
|
||||
ContactsContract.CommonDataKinds.StructuredName.SUFFIX,
|
||||
ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME };
|
||||
String selection = ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?";
|
||||
String[] args = new String[] { String.valueOf(contactId), ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE };
|
||||
|
||||
return context.getContentResolver().query(ContactsContract.Data.CONTENT_URI,
|
||||
projection,
|
||||
selection,
|
||||
args,
|
||||
null);
|
||||
}
|
||||
|
||||
public @Nullable String getOrganizationName(long contactId) {
|
||||
String[] projection = new String[] { ContactsContract.CommonDataKinds.Organization.COMPANY };
|
||||
String selection = ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?";
|
||||
String[] args = new String[] { String.valueOf(contactId), ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE };
|
||||
|
||||
try (Cursor cursor = context.getContentResolver().query(ContactsContract.Data.CONTENT_URI,
|
||||
projection,
|
||||
selection,
|
||||
args,
|
||||
null))
|
||||
{
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return cursor.getString(0);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public @Nullable Cursor getPhoneDetails(long contactId) {
|
||||
String[] projection = new String[] { ContactsContract.CommonDataKinds.Phone.NUMBER,
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE,
|
||||
ContactsContract.CommonDataKinds.Phone.LABEL };
|
||||
String selection = ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?";
|
||||
String[] args = new String[] { String.valueOf(contactId), ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE };
|
||||
|
||||
return context.getContentResolver().query(ContactsContract.Data.CONTENT_URI,
|
||||
projection,
|
||||
selection,
|
||||
args,
|
||||
null);
|
||||
}
|
||||
|
||||
public @Nullable Cursor getEmailDetails(long contactId) {
|
||||
String[] projection = new String[] { ContactsContract.CommonDataKinds.Email.ADDRESS,
|
||||
ContactsContract.CommonDataKinds.Email.TYPE,
|
||||
ContactsContract.CommonDataKinds.Email.LABEL };
|
||||
String selection = ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?";
|
||||
String[] args = new String[] { String.valueOf(contactId), ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE };
|
||||
|
||||
return context.getContentResolver().query(ContactsContract.Data.CONTENT_URI,
|
||||
projection,
|
||||
selection,
|
||||
args,
|
||||
null);
|
||||
}
|
||||
|
||||
public @Nullable Cursor getPostalAddressDetails(long contactId) {
|
||||
String[] projection = new String[] { ContactsContract.CommonDataKinds.StructuredPostal.TYPE,
|
||||
ContactsContract.CommonDataKinds.StructuredPostal.LABEL,
|
||||
ContactsContract.CommonDataKinds.StructuredPostal.STREET,
|
||||
ContactsContract.CommonDataKinds.StructuredPostal.POBOX,
|
||||
ContactsContract.CommonDataKinds.StructuredPostal.NEIGHBORHOOD,
|
||||
ContactsContract.CommonDataKinds.StructuredPostal.CITY,
|
||||
ContactsContract.CommonDataKinds.StructuredPostal.REGION,
|
||||
ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE,
|
||||
ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY };
|
||||
String selection = ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?";
|
||||
String[] args = new String[] { String.valueOf(contactId), ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE };
|
||||
|
||||
return context.getContentResolver().query(ContactsContract.Data.CONTENT_URI,
|
||||
projection,
|
||||
selection,
|
||||
args,
|
||||
null);
|
||||
}
|
||||
|
||||
public @Nullable Uri getAvatarUri(long contactId) {
|
||||
String[] projection = new String[] { ContactsContract.CommonDataKinds.Photo.PHOTO_URI };
|
||||
String selection = ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?";
|
||||
String[] args = new String[] { String.valueOf(contactId), ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE };
|
||||
|
||||
try (Cursor cursor = context.getContentResolver().query(ContactsContract.Data.CONTENT_URI,
|
||||
projection,
|
||||
selection,
|
||||
args,
|
||||
null))
|
||||
{
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
String uri = cursor.getString(0);
|
||||
if (uri != null) {
|
||||
return Uri.parse(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void addContactVoiceSupport(List<ContentProviderOperation> operations,
|
||||
@NonNull String address, long rawContactId)
|
||||
{
|
||||
operations.add(ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI)
|
||||
.withSelection(RawContacts._ID + " = ?", new String[] {String.valueOf(rawContactId)})
|
||||
.withValue(RawContacts.SYNC4, "true")
|
||||
.build());
|
||||
|
||||
operations.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI.buildUpon().appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build())
|
||||
.withValue(ContactsContract.Data.RAW_CONTACT_ID, rawContactId)
|
||||
.withValue(ContactsContract.Data.MIMETYPE, CALL_MIMETYPE)
|
||||
.withValue(ContactsContract.Data.DATA1, address)
|
||||
.withValue(ContactsContract.Data.DATA2, context.getString(R.string.app_name))
|
||||
.withValue(ContactsContract.Data.DATA3, context.getString(R.string.ContactsDatabase_signal_call_s, address))
|
||||
.withYieldAllowed(true)
|
||||
.build());
|
||||
}
|
||||
|
||||
private void updateDisplayName(List<ContentProviderOperation> operations,
|
||||
@Nullable String displayName,
|
||||
long rawContactId, int displayNameSource)
|
||||
{
|
||||
Uri dataUri = ContactsContract.Data.CONTENT_URI.buildUpon()
|
||||
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
|
||||
.build();
|
||||
|
||||
if (displayNameSource != ContactsContract.DisplayNameSources.STRUCTURED_NAME) {
|
||||
operations.add(ContentProviderOperation.newInsert(dataUri)
|
||||
.withValue(ContactsContract.CommonDataKinds.StructuredName.RAW_CONTACT_ID, rawContactId)
|
||||
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName)
|
||||
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
|
||||
.build());
|
||||
} else {
|
||||
operations.add(ContentProviderOperation.newUpdate(dataUri)
|
||||
.withSelection(ContactsContract.CommonDataKinds.StructuredName.RAW_CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?",
|
||||
new String[] {String.valueOf(rawContactId), ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE})
|
||||
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName)
|
||||
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
private void addTextSecureRawContact(List<ContentProviderOperation> operations,
|
||||
Account account, String e164number, String displayName,
|
||||
long aggregateId)
|
||||
{
|
||||
int index = operations.size();
|
||||
Uri dataUri = ContactsContract.Data.CONTENT_URI.buildUpon()
|
||||
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
|
||||
.build();
|
||||
|
||||
operations.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
|
||||
.withValue(RawContacts.ACCOUNT_NAME, account.name)
|
||||
.withValue(RawContacts.ACCOUNT_TYPE, account.type)
|
||||
.withValue(RawContacts.SYNC1, e164number)
|
||||
.withValue(RawContacts.SYNC4, String.valueOf(true))
|
||||
.build());
|
||||
|
||||
operations.add(ContentProviderOperation.newInsert(dataUri)
|
||||
.withValueBackReference(ContactsContract.CommonDataKinds.StructuredName.RAW_CONTACT_ID, index)
|
||||
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName)
|
||||
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
|
||||
.build());
|
||||
|
||||
operations.add(ContentProviderOperation.newInsert(dataUri)
|
||||
.withValueBackReference(ContactsContract.CommonDataKinds.Phone.RAW_CONTACT_ID, index)
|
||||
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
|
||||
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, e164number)
|
||||
.withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_OTHER)
|
||||
.withValue(ContactsContract.Data.SYNC2, SYNC)
|
||||
.build());
|
||||
|
||||
operations.add(ContentProviderOperation.newInsert(dataUri)
|
||||
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index)
|
||||
.withValue(ContactsContract.Data.MIMETYPE, CONTACT_MIMETYPE)
|
||||
.withValue(ContactsContract.Data.DATA1, e164number)
|
||||
.withValue(ContactsContract.Data.DATA2, context.getString(R.string.app_name))
|
||||
.withValue(ContactsContract.Data.DATA3, context.getString(R.string.ContactsDatabase_message_s, e164number))
|
||||
.withYieldAllowed(true)
|
||||
.build());
|
||||
|
||||
operations.add(ContentProviderOperation.newInsert(dataUri)
|
||||
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index)
|
||||
.withValue(ContactsContract.Data.MIMETYPE, CALL_MIMETYPE)
|
||||
.withValue(ContactsContract.Data.DATA1, e164number)
|
||||
.withValue(ContactsContract.Data.DATA2, context.getString(R.string.app_name))
|
||||
.withValue(ContactsContract.Data.DATA3, context.getString(R.string.ContactsDatabase_signal_call_s, e164number))
|
||||
.withYieldAllowed(true)
|
||||
.build());
|
||||
|
||||
operations.add(ContentProviderOperation.newUpdate(ContactsContract.AggregationExceptions.CONTENT_URI)
|
||||
.withValue(ContactsContract.AggregationExceptions.RAW_CONTACT_ID1, aggregateId)
|
||||
.withValueBackReference(ContactsContract.AggregationExceptions.RAW_CONTACT_ID2, index)
|
||||
.withValue(ContactsContract.AggregationExceptions.TYPE, ContactsContract.AggregationExceptions.TYPE_KEEP_TOGETHER)
|
||||
.build());
|
||||
}
|
||||
|
||||
private void removeTextSecureRawContact(List<ContentProviderOperation> operations,
|
||||
Account account, long rowId)
|
||||
{
|
||||
operations.add(ContentProviderOperation.newDelete(RawContacts.CONTENT_URI.buildUpon()
|
||||
.appendQueryParameter(RawContacts.ACCOUNT_NAME, account.name)
|
||||
.appendQueryParameter(RawContacts.ACCOUNT_TYPE, account.type)
|
||||
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build())
|
||||
.withYieldAllowed(true)
|
||||
.withSelection(BaseColumns._ID + " = ?", new String[] {String.valueOf(rowId)})
|
||||
.build());
|
||||
}
|
||||
|
||||
private @NonNull Map<String, SignalContact> getSignalRawContacts(@NonNull Account account) {
|
||||
Uri currentContactsUri = RawContacts.CONTENT_URI.buildUpon()
|
||||
.appendQueryParameter(RawContacts.ACCOUNT_NAME, account.name)
|
||||
.appendQueryParameter(RawContacts.ACCOUNT_TYPE, account.type).build();
|
||||
|
||||
Map<String, SignalContact> signalContacts = new HashMap<>();
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
String[] projection = new String[] {BaseColumns._ID, RawContacts.SYNC1, RawContacts.SYNC4, RawContacts.CONTACT_ID, RawContacts.DISPLAY_NAME_PRIMARY, RawContacts.DISPLAY_NAME_SOURCE};
|
||||
|
||||
cursor = context.getContentResolver().query(currentContactsUri, projection, null, null, null);
|
||||
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
String currentAddress = PhoneNumberFormatter.get(context).format(cursor.getString(1));
|
||||
long rawContactId = cursor.getLong(0);
|
||||
long contactId = cursor.getLong(3);
|
||||
String supportsVoice = cursor.getString(2);
|
||||
String rawContactDisplayName = cursor.getString(4);
|
||||
String aggregateDisplayName = getDisplayName(contactId);
|
||||
int rawContactDisplayNameSource = cursor.getInt(5);
|
||||
|
||||
signalContacts.put(currentAddress, new SignalContact(rawContactId, supportsVoice, rawContactDisplayName, aggregateDisplayName, rawContactDisplayNameSource));
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
return signalContacts;
|
||||
}
|
||||
|
||||
private Optional<SystemContactInfo> getSystemContactInfo(@NonNull String address)
|
||||
{
|
||||
Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(address));
|
||||
String[] projection = {ContactsContract.PhoneLookup.NUMBER,
|
||||
ContactsContract.PhoneLookup._ID,
|
||||
ContactsContract.PhoneLookup.DISPLAY_NAME};
|
||||
Cursor numberCursor = null;
|
||||
Cursor idCursor = null;
|
||||
|
||||
try {
|
||||
numberCursor = context.getContentResolver().query(uri, projection, null, null, null);
|
||||
|
||||
while (numberCursor != null && numberCursor.moveToNext()) {
|
||||
String systemNumber = numberCursor.getString(0);
|
||||
String systemAddress = PhoneNumberFormatter.get(context).format(systemNumber);
|
||||
|
||||
if (systemAddress.equals(address)) {
|
||||
idCursor = context.getContentResolver().query(RawContacts.CONTENT_URI,
|
||||
new String[] {RawContacts._ID},
|
||||
RawContacts.CONTACT_ID + " = ? ",
|
||||
new String[] {String.valueOf(numberCursor.getLong(1))},
|
||||
null);
|
||||
|
||||
if (idCursor != null && idCursor.moveToNext()) {
|
||||
return Optional.of(new SystemContactInfo(numberCursor.getString(2),
|
||||
numberCursor.getString(0),
|
||||
idCursor.getLong(0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (numberCursor != null) numberCursor.close();
|
||||
if (idCursor != null) idCursor.close();
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private @Nullable String getDisplayName(long contactId) {
|
||||
Cursor cursor = context.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI,
|
||||
new String[]{ContactsContract.Contacts.DISPLAY_NAME},
|
||||
ContactsContract.Contacts._ID + " = ?",
|
||||
new String[] {String.valueOf(contactId)},
|
||||
null);
|
||||
|
||||
try {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return cursor.getString(0);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void applyOperationsInBatches(@NonNull ContentResolver contentResolver,
|
||||
@NonNull String authority,
|
||||
@NonNull List<ContentProviderOperation> operations,
|
||||
int batchSize)
|
||||
throws OperationApplicationException, RemoteException
|
||||
{
|
||||
List<List<ContentProviderOperation>> batches = Util.chunk(operations, batchSize);
|
||||
for (List<ContentProviderOperation> batch : batches) {
|
||||
contentResolver.applyBatch(authority, new ArrayList<>(batch));
|
||||
}
|
||||
}
|
||||
|
||||
private static class SystemContactInfo {
|
||||
private final String name;
|
||||
private final String number;
|
||||
private final long id;
|
||||
|
||||
private SystemContactInfo(String name, String number, long id) {
|
||||
this.name = name;
|
||||
this.number = number;
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
private static class SignalContact {
|
||||
|
||||
private final long id;
|
||||
@Nullable private final String supportsVoice;
|
||||
@Nullable private final String rawDisplayName;
|
||||
@Nullable private final String aggregateDisplayName;
|
||||
private final int displayNameSource;
|
||||
|
||||
SignalContact(long id,
|
||||
@Nullable String supportsVoice,
|
||||
@Nullable String rawDisplayName,
|
||||
@Nullable String aggregateDisplayName,
|
||||
int displayNameSource)
|
||||
{
|
||||
this.id = id;
|
||||
this.supportsVoice = supportsVoice;
|
||||
this.rawDisplayName = rawDisplayName;
|
||||
this.aggregateDisplayName = aggregateDisplayName;
|
||||
this.displayNameSource = displayNameSource;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
boolean isVoiceSupported() {
|
||||
return "true".equals(supportsVoice);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String getRawDisplayName() {
|
||||
return rawDisplayName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String getAggregateDisplayName() {
|
||||
return aggregateDisplayName;
|
||||
}
|
||||
|
||||
int getDisplayNameSource() {
|
||||
return displayNameSource;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,10 +19,7 @@ import com.annimon.stream.Collectors;
|
|||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.contacts.ContactAccessor;
|
||||
import org.thoughtcrime.securesms.contacts.ContactsDatabase;
|
||||
import org.thoughtcrime.securesms.database.MessageDatabase.InsertResult;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase.BulkOperationsHandle;
|
||||
|
@ -307,7 +304,7 @@ class DirectoryHelper {
|
|||
return;
|
||||
}
|
||||
|
||||
AccountHolder account = getOrCreateSystemAccount(context);
|
||||
Account account = SystemContactsRepository.getOrCreateSystemAccount(context);
|
||||
|
||||
if (account == null) {
|
||||
Log.w(TAG, "Failed to create an account!");
|
||||
|
@ -315,15 +312,14 @@ class DirectoryHelper {
|
|||
}
|
||||
|
||||
try {
|
||||
ContactsDatabase contactsDatabase = SignalDatabase.contacts();
|
||||
List<String> activeAddresses = Stream.of(activeIds)
|
||||
.map(Recipient::resolved)
|
||||
.filter(Recipient::hasE164)
|
||||
.map(Recipient::requireE164)
|
||||
.toList();
|
||||
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);
|
||||
SystemContactsRepository.removeDeletedRawContacts(context, account);
|
||||
SystemContactsRepository.setRegisteredUsers(context, account, activeAddresses, removeMissing);
|
||||
|
||||
syncRecipientInfoWithSystemContacts(context, rewrites);
|
||||
} catch (RemoteException | OperationApplicationException e) {
|
||||
|
@ -425,39 +421,6 @@ class DirectoryHelper {
|
|||
return CursorUtil.requireString(cursor, ContactsContract.Data.MIMETYPE);
|
||||
}
|
||||
|
||||
private static @Nullable AccountHolder getOrCreateSystemAccount(Context context) {
|
||||
AccountManager accountManager = AccountManager.get(context);
|
||||
Account[] accounts = accountManager.getAccountsByType(BuildConfig.APPLICATION_ID);
|
||||
|
||||
AccountHolder account;
|
||||
|
||||
if (accounts.length == 0) {
|
||||
account = createAccount(context);
|
||||
} else {
|
||||
account = new AccountHolder(accounts[0], false);
|
||||
}
|
||||
|
||||
if (account != null && !ContentResolver.getSyncAutomatically(account.getAccount(), ContactsContract.AUTHORITY)) {
|
||||
ContentResolver.setSyncAutomatically(account.getAccount(), ContactsContract.AUTHORITY, true);
|
||||
}
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
private static @Nullable AccountHolder createAccount(Context context) {
|
||||
AccountManager accountManager = AccountManager.get(context);
|
||||
Account account = new Account(context.getString(R.string.app_name), BuildConfig.APPLICATION_ID);
|
||||
|
||||
if (accountManager.addAccountExplicitly(account, null, null)) {
|
||||
Log.i(TAG, "Created new account...");
|
||||
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1);
|
||||
return new AccountHolder(account, true);
|
||||
} else {
|
||||
Log.w(TAG, "Failed to create account!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void notifyNewUsers(@NonNull Context context,
|
||||
@NonNull Collection<RecipientId> newUsers)
|
||||
{
|
||||
|
@ -611,23 +574,4 @@ class DirectoryHelper {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class AccountHolder {
|
||||
private final boolean fresh;
|
||||
private final Account account;
|
||||
|
||||
private AccountHolder(Account account, boolean fresh) {
|
||||
this.fresh = fresh;
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public boolean isFresh() {
|
||||
return fresh;
|
||||
}
|
||||
|
||||
public Account getAccount() {
|
||||
return account;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,539 @@
|
|||
package org.thoughtcrime.securesms.contacts.sync
|
||||
|
||||
import android.accounts.Account
|
||||
import android.accounts.AccountManager
|
||||
import android.content.ContentProviderOperation
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.content.OperationApplicationException
|
||||
import android.net.Uri
|
||||
import android.os.RemoteException
|
||||
import android.provider.BaseColumns
|
||||
import android.provider.ContactsContract
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.BuildConfig
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.database.requireInt
|
||||
import org.thoughtcrime.securesms.database.requireString
|
||||
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
|
||||
import org.thoughtcrime.securesms.util.SqlUtil
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import java.util.ArrayList
|
||||
import java.util.HashMap
|
||||
|
||||
/**
|
||||
* A way to retrieve and update data in the Android system contacts.
|
||||
*/
|
||||
object SystemContactsRepository {
|
||||
|
||||
private val TAG = Log.tag(SystemContactsRepository::class.java)
|
||||
private const val CONTACT_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 SYNC = "__TS"
|
||||
|
||||
@JvmStatic
|
||||
fun getOrCreateSystemAccount(context: Context): Account? {
|
||||
val accountManager: AccountManager = AccountManager.get(context)
|
||||
val accounts: Array<Account> = accountManager.getAccountsByType(BuildConfig.APPLICATION_ID)
|
||||
var account: Account? = if (accounts.isNotEmpty()) accounts[0] else null
|
||||
|
||||
if (account == null) {
|
||||
Log.i(TAG, "Attempting to create a new account...")
|
||||
val newAccount = Account(context.getString(R.string.app_name), BuildConfig.APPLICATION_ID)
|
||||
|
||||
if (accountManager.addAccountExplicitly(newAccount, null, null)) {
|
||||
Log.i(TAG, "Successfully created a new account.")
|
||||
ContentResolver.setIsSyncable(newAccount, ContactsContract.AUTHORITY, 1)
|
||||
account = newAccount
|
||||
} else {
|
||||
Log.w(TAG, "Failed to create a new account!")
|
||||
}
|
||||
}
|
||||
|
||||
if (account != null && !ContentResolver.getSyncAutomatically(account, ContactsContract.AUTHORITY)) {
|
||||
Log.i(TAG, "Updated account to sync automatically.")
|
||||
ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true)
|
||||
}
|
||||
|
||||
return account
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Synchronized
|
||||
fun removeDeletedRawContacts(context: Context, account: Account) {
|
||||
val currentContactsUri = ContactsContract.RawContacts.CONTENT_URI.buildUpon()
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, account.name)
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type)
|
||||
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
|
||||
.build()
|
||||
|
||||
val projection = arrayOf(BaseColumns._ID, ContactsContract.RawContacts.SYNC1)
|
||||
|
||||
context.contentResolver.query(currentContactsUri, projection, "${ContactsContract.RawContacts.DELETED} = ?", SqlUtil.buildArgs(1), null)?.use { cursor ->
|
||||
while (cursor.moveToNext()) {
|
||||
val rawContactId = cursor.getLong(0)
|
||||
|
||||
Log.i(TAG, """Deleting raw contact: ${cursor.getString(1)}, $rawContactId""")
|
||||
context.contentResolver.delete(currentContactsUri, "${ContactsContract.RawContacts._ID} = ?", arrayOf(rawContactId.toString()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Synchronized
|
||||
@Throws(RemoteException::class, OperationApplicationException::class)
|
||||
fun setRegisteredUsers(
|
||||
context: Context,
|
||||
account: Account,
|
||||
registeredAddressList: List<String>,
|
||||
remove: Boolean
|
||||
) {
|
||||
val registeredAddressSet: Set<String> = registeredAddressList.toSet()
|
||||
val operations: ArrayList<ContentProviderOperation> = ArrayList()
|
||||
val currentContacts: Map<String, SignalContact> = getSignalRawContacts(context, account)
|
||||
|
||||
val registeredChunks: List<List<String>> = Util.chunk(registeredAddressList, 50)
|
||||
for (registeredChunk in registeredChunks) {
|
||||
for (registeredAddress in registeredChunk) {
|
||||
if (!currentContacts.containsKey(registeredAddress)) {
|
||||
val systemContactInfo: SystemContactInfo? = getSystemContactInfo(context, registeredAddress)
|
||||
if (systemContactInfo != null) {
|
||||
Log.i(TAG, "Adding number: $registeredAddress")
|
||||
addTextSecureRawContact(
|
||||
context = context,
|
||||
operations = operations,
|
||||
account = account,
|
||||
e164number = systemContactInfo.number,
|
||||
displayName = systemContactInfo.name,
|
||||
aggregateId = systemContactInfo.id
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (operations.isNotEmpty()) {
|
||||
context.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
|
||||
operations.clear()
|
||||
}
|
||||
}
|
||||
|
||||
for ((key, value) in currentContacts) {
|
||||
if (!registeredAddressSet.contains(key)) {
|
||||
if (remove) {
|
||||
Log.i(TAG, "Removing number: $key")
|
||||
removeTextSecureRawContact(operations, account, value.id)
|
||||
}
|
||||
} else if (!value.isVoiceSupported()) {
|
||||
Log.i(TAG, "Adding voice support: $key")
|
||||
addContactVoiceSupport(context, operations, key, value.id)
|
||||
} else if (!Util.isStringEquals(value.rawDisplayName, value.aggregateDisplayName)) {
|
||||
Log.i(TAG, "Updating display name: $key")
|
||||
updateDisplayName(operations, value.aggregateDisplayName, value.id, value.displayNameSource)
|
||||
}
|
||||
}
|
||||
|
||||
if (operations.isNotEmpty()) {
|
||||
applyOperationsInBatches(context.contentResolver, ContactsContract.AUTHORITY, operations, 50)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getNameDetails(context: Context, contactId: Long): NameDetails? {
|
||||
val projection = arrayOf(
|
||||
ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,
|
||||
ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME,
|
||||
ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME,
|
||||
ContactsContract.CommonDataKinds.StructuredName.PREFIX,
|
||||
ContactsContract.CommonDataKinds.StructuredName.SUFFIX,
|
||||
ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME
|
||||
)
|
||||
val selection = "${ContactsContract.Data.CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ?"
|
||||
val args = SqlUtil.buildArgs(contactId, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
|
||||
|
||||
return context.contentResolver.query(ContactsContract.Data.CONTENT_URI, projection, selection, args, null)?.use { cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
NameDetails(
|
||||
displayName = cursor.requireString(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME),
|
||||
givenName = cursor.requireString(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME),
|
||||
familyName = cursor.requireString(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME),
|
||||
prefix = cursor.requireString(ContactsContract.CommonDataKinds.StructuredName.PREFIX),
|
||||
suffix = cursor.requireString(ContactsContract.CommonDataKinds.StructuredName.SUFFIX),
|
||||
middleName = cursor.requireString(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME)
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getOrganizationName(context: Context, contactId: Long): String? {
|
||||
val projection = arrayOf(ContactsContract.CommonDataKinds.Organization.COMPANY)
|
||||
val selection = "${ContactsContract.Data.CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ?"
|
||||
val args = SqlUtil.buildArgs(contactId, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE)
|
||||
|
||||
context.contentResolver.query(ContactsContract.Data.CONTENT_URI, projection, selection, args, null)?.use { cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
return cursor.getString(0)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getPhoneDetails(context: Context, contactId: Long): List<PhoneDetails> {
|
||||
val projection = arrayOf(
|
||||
ContactsContract.CommonDataKinds.Phone.NUMBER,
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE,
|
||||
ContactsContract.CommonDataKinds.Phone.LABEL
|
||||
)
|
||||
val selection = "${ContactsContract.Data.CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ?"
|
||||
val args = SqlUtil.buildArgs(contactId, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
|
||||
|
||||
val phoneDetails: MutableList<PhoneDetails> = mutableListOf()
|
||||
|
||||
context.contentResolver.query(ContactsContract.Data.CONTENT_URI, projection, selection, args, null)?.use { cursor ->
|
||||
while (cursor.moveToNext()) {
|
||||
phoneDetails += PhoneDetails(
|
||||
number = cursor.requireString(ContactsContract.CommonDataKinds.Phone.NUMBER),
|
||||
type = cursor.requireInt(ContactsContract.CommonDataKinds.Phone.TYPE),
|
||||
label = cursor.requireString(ContactsContract.CommonDataKinds.Phone.LABEL)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return phoneDetails
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getEmailDetails(context: Context, contactId: Long): List<EmailDetails> {
|
||||
val projection = arrayOf(
|
||||
ContactsContract.CommonDataKinds.Email.ADDRESS,
|
||||
ContactsContract.CommonDataKinds.Email.TYPE,
|
||||
ContactsContract.CommonDataKinds.Email.LABEL
|
||||
)
|
||||
val selection = "${ContactsContract.Data.CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ?"
|
||||
val args = SqlUtil.buildArgs(contactId, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
|
||||
|
||||
val emailDetails: MutableList<EmailDetails> = mutableListOf()
|
||||
context.contentResolver.query(ContactsContract.Data.CONTENT_URI, projection, selection, args, null)?.use { cursor ->
|
||||
while (cursor.moveToNext()) {
|
||||
emailDetails += EmailDetails(
|
||||
address = cursor.requireString(ContactsContract.CommonDataKinds.Email.ADDRESS),
|
||||
type = cursor.requireInt(ContactsContract.CommonDataKinds.Email.TYPE),
|
||||
label = cursor.requireString(ContactsContract.CommonDataKinds.Email.LABEL)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return emailDetails
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getPostalAddressDetails(context: Context, contactId: Long): List<PostalAddressDetails> {
|
||||
val projection = arrayOf(
|
||||
ContactsContract.CommonDataKinds.StructuredPostal.TYPE,
|
||||
ContactsContract.CommonDataKinds.StructuredPostal.LABEL,
|
||||
ContactsContract.CommonDataKinds.StructuredPostal.STREET,
|
||||
ContactsContract.CommonDataKinds.StructuredPostal.POBOX,
|
||||
ContactsContract.CommonDataKinds.StructuredPostal.NEIGHBORHOOD,
|
||||
ContactsContract.CommonDataKinds.StructuredPostal.CITY,
|
||||
ContactsContract.CommonDataKinds.StructuredPostal.REGION,
|
||||
ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE,
|
||||
ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY
|
||||
)
|
||||
val selection = "${ContactsContract.Data.CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ?"
|
||||
val args = SqlUtil.buildArgs(contactId, ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE)
|
||||
|
||||
val postalDetails: MutableList<PostalAddressDetails> = mutableListOf()
|
||||
|
||||
context.contentResolver.query(ContactsContract.Data.CONTENT_URI, projection, selection, args, null)?.use { cursor ->
|
||||
while (cursor.moveToNext()) {
|
||||
postalDetails += PostalAddressDetails(
|
||||
type = cursor.requireInt(ContactsContract.CommonDataKinds.StructuredPostal.TYPE),
|
||||
label = cursor.requireString(ContactsContract.CommonDataKinds.StructuredPostal.LABEL),
|
||||
street = cursor.requireString(ContactsContract.CommonDataKinds.StructuredPostal.STREET),
|
||||
poBox = cursor.requireString(ContactsContract.CommonDataKinds.StructuredPostal.POBOX),
|
||||
neighborhood = cursor.requireString(ContactsContract.CommonDataKinds.StructuredPostal.NEIGHBORHOOD),
|
||||
city = cursor.requireString(ContactsContract.CommonDataKinds.StructuredPostal.CITY),
|
||||
region = cursor.requireString(ContactsContract.CommonDataKinds.StructuredPostal.REGION),
|
||||
postal = cursor.requireString(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE),
|
||||
country = cursor.requireString(ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return postalDetails
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getAvatarUri(context: Context, contactId: Long): Uri? {
|
||||
val projection = arrayOf(ContactsContract.CommonDataKinds.Photo.PHOTO_URI)
|
||||
val selection = "${ContactsContract.Data.CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ?"
|
||||
val args = SqlUtil.buildArgs(contactId, ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
|
||||
|
||||
context.contentResolver.query(ContactsContract.Data.CONTENT_URI, projection, selection, args, null)?.use { cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
val uri = cursor.getString(0)
|
||||
if (uri != null) {
|
||||
return Uri.parse(uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun addContactVoiceSupport(context: Context, operations: MutableList<ContentProviderOperation>, address: String, rawContactId: Long) {
|
||||
operations.add(
|
||||
ContentProviderOperation.newUpdate(ContactsContract.RawContacts.CONTENT_URI)
|
||||
.withSelection("${ContactsContract.RawContacts._ID} = ?", arrayOf(rawContactId.toString()))
|
||||
.withValue(ContactsContract.RawContacts.SYNC4, "true")
|
||||
.build()
|
||||
)
|
||||
|
||||
operations.add(
|
||||
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI.buildUpon().appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build())
|
||||
.withValue(ContactsContract.Data.RAW_CONTACT_ID, rawContactId)
|
||||
.withValue(ContactsContract.Data.MIMETYPE, CALL_MIMETYPE)
|
||||
.withValue(ContactsContract.Data.DATA1, address)
|
||||
.withValue(ContactsContract.Data.DATA2, context.getString(R.string.app_name))
|
||||
.withValue(ContactsContract.Data.DATA3, context.getString(R.string.ContactsDatabase_signal_call_s, address))
|
||||
.withYieldAllowed(true)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateDisplayName(operations: MutableList<ContentProviderOperation>, displayName: String?, rawContactId: Long, displayNameSource: Int) {
|
||||
val dataUri = ContactsContract.Data.CONTENT_URI.buildUpon()
|
||||
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
|
||||
.build()
|
||||
|
||||
if (displayNameSource != ContactsContract.DisplayNameSources.STRUCTURED_NAME) {
|
||||
operations.add(
|
||||
ContentProviderOperation.newInsert(dataUri)
|
||||
.withValue(ContactsContract.CommonDataKinds.StructuredName.RAW_CONTACT_ID, rawContactId)
|
||||
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName)
|
||||
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
|
||||
.build()
|
||||
)
|
||||
} else {
|
||||
operations.add(
|
||||
ContentProviderOperation.newUpdate(dataUri)
|
||||
.withSelection("${ContactsContract.CommonDataKinds.StructuredName.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ?", SqlUtil.buildArgs(rawContactId.toString(), ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE))
|
||||
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName)
|
||||
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addTextSecureRawContact(
|
||||
context: Context,
|
||||
operations: MutableList<ContentProviderOperation>,
|
||||
account: Account,
|
||||
e164number: String,
|
||||
displayName: String,
|
||||
aggregateId: Long
|
||||
) {
|
||||
val index = operations.size
|
||||
val dataUri = ContactsContract.Data.CONTENT_URI.buildUpon()
|
||||
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
|
||||
.build()
|
||||
|
||||
operations.add(
|
||||
ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
|
||||
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, account.name)
|
||||
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type)
|
||||
.withValue(ContactsContract.RawContacts.SYNC1, e164number)
|
||||
.withValue(ContactsContract.RawContacts.SYNC4, true.toString())
|
||||
.build()
|
||||
)
|
||||
operations.add(
|
||||
ContentProviderOperation.newInsert(dataUri)
|
||||
.withValueBackReference(ContactsContract.CommonDataKinds.StructuredName.RAW_CONTACT_ID, index)
|
||||
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName)
|
||||
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
|
||||
.build()
|
||||
)
|
||||
operations.add(
|
||||
ContentProviderOperation.newInsert(dataUri)
|
||||
.withValueBackReference(ContactsContract.CommonDataKinds.Phone.RAW_CONTACT_ID, index)
|
||||
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
|
||||
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, e164number)
|
||||
.withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_OTHER)
|
||||
.withValue(ContactsContract.Data.SYNC2, SYNC)
|
||||
.build()
|
||||
)
|
||||
operations.add(
|
||||
ContentProviderOperation.newInsert(dataUri)
|
||||
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index)
|
||||
.withValue(ContactsContract.Data.MIMETYPE, CONTACT_MIMETYPE)
|
||||
.withValue(ContactsContract.Data.DATA1, e164number)
|
||||
.withValue(ContactsContract.Data.DATA2, context.getString(R.string.app_name))
|
||||
.withValue(ContactsContract.Data.DATA3, context.getString(R.string.ContactsDatabase_message_s, e164number))
|
||||
.withYieldAllowed(true)
|
||||
.build()
|
||||
)
|
||||
operations.add(
|
||||
ContentProviderOperation.newInsert(dataUri)
|
||||
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index)
|
||||
.withValue(ContactsContract.Data.MIMETYPE, CALL_MIMETYPE)
|
||||
.withValue(ContactsContract.Data.DATA1, e164number)
|
||||
.withValue(ContactsContract.Data.DATA2, context.getString(R.string.app_name))
|
||||
.withValue(ContactsContract.Data.DATA3, context.getString(R.string.ContactsDatabase_signal_call_s, e164number))
|
||||
.withYieldAllowed(true)
|
||||
.build()
|
||||
)
|
||||
operations.add(
|
||||
ContentProviderOperation.newUpdate(ContactsContract.AggregationExceptions.CONTENT_URI)
|
||||
.withValue(ContactsContract.AggregationExceptions.RAW_CONTACT_ID1, aggregateId)
|
||||
.withValueBackReference(ContactsContract.AggregationExceptions.RAW_CONTACT_ID2, index)
|
||||
.withValue(ContactsContract.AggregationExceptions.TYPE, ContactsContract.AggregationExceptions.TYPE_KEEP_TOGETHER)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
private fun removeTextSecureRawContact(operations: MutableList<ContentProviderOperation>, account: Account, rowId: Long) {
|
||||
operations.add(
|
||||
ContentProviderOperation.newDelete(
|
||||
ContactsContract.RawContacts.CONTENT_URI.buildUpon()
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, account.name)
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type)
|
||||
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build()
|
||||
)
|
||||
.withYieldAllowed(true)
|
||||
.withSelection("${BaseColumns._ID} = ?", SqlUtil.buildArgs(rowId))
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
private fun getSignalRawContacts(context: Context, account: Account): Map<String, SignalContact> {
|
||||
val currentContactsUri = ContactsContract.RawContacts.CONTENT_URI.buildUpon()
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, account.name)
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type).build()
|
||||
val projection = arrayOf(BaseColumns._ID, ContactsContract.RawContacts.SYNC1, ContactsContract.RawContacts.SYNC4, ContactsContract.RawContacts.CONTACT_ID, ContactsContract.RawContacts.DISPLAY_NAME_PRIMARY, ContactsContract.RawContacts.DISPLAY_NAME_SOURCE)
|
||||
|
||||
val signalContacts: MutableMap<String, SignalContact> = HashMap()
|
||||
|
||||
context.contentResolver.query(currentContactsUri, projection, null, null, null)?.use { cursor ->
|
||||
while (cursor.moveToNext()) {
|
||||
val currentAddress = PhoneNumberFormatter.get(context).format(cursor.getString(1))
|
||||
|
||||
signalContacts[currentAddress] = SignalContact(
|
||||
id = cursor.getLong(0),
|
||||
supportsVoice = cursor.getString(2),
|
||||
rawDisplayName = cursor.getString(4),
|
||||
aggregateDisplayName = getDisplayName(context, cursor.getLong(3)),
|
||||
displayNameSource = cursor.getInt(5)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return signalContacts
|
||||
}
|
||||
|
||||
private fun getSystemContactInfo(context: Context, address: String): SystemContactInfo? {
|
||||
val uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(address))
|
||||
val projection = arrayOf(
|
||||
ContactsContract.PhoneLookup.NUMBER,
|
||||
ContactsContract.PhoneLookup._ID,
|
||||
ContactsContract.PhoneLookup.DISPLAY_NAME
|
||||
)
|
||||
|
||||
context.contentResolver.query(uri, projection, null, null, null)?.use { numberCursor ->
|
||||
while (numberCursor.moveToNext()) {
|
||||
val systemNumber = numberCursor.getString(0)
|
||||
val systemAddress = PhoneNumberFormatter.get(context).format(systemNumber)
|
||||
if (systemAddress == address) {
|
||||
context.contentResolver.query(ContactsContract.RawContacts.CONTENT_URI, arrayOf(ContactsContract.RawContacts._ID), "${ContactsContract.RawContacts.CONTACT_ID} = ? ", SqlUtil.buildArgs(numberCursor.getLong(1)), null)?.use { idCursor ->
|
||||
if (idCursor.moveToNext()) {
|
||||
return SystemContactInfo(
|
||||
name = numberCursor.getString(2),
|
||||
number = numberCursor.getString(0),
|
||||
id = idCursor.getLong(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getDisplayName(context: Context, contactId: Long): String? {
|
||||
val projection = arrayOf(ContactsContract.Contacts.DISPLAY_NAME)
|
||||
val selection = "${ContactsContract.Contacts._ID} = ?"
|
||||
val args = SqlUtil.buildArgs(contactId)
|
||||
|
||||
context.contentResolver.query(ContactsContract.Contacts.CONTENT_URI, projection, selection, args, null)?.use { cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
return cursor.getString(0)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@Throws(OperationApplicationException::class, RemoteException::class)
|
||||
private fun applyOperationsInBatches(
|
||||
contentResolver: ContentResolver,
|
||||
authority: String,
|
||||
operations: List<ContentProviderOperation>,
|
||||
batchSize: Int
|
||||
) {
|
||||
val batches = Util.chunk(operations, batchSize)
|
||||
for (batch in batches) {
|
||||
contentResolver.applyBatch(authority, ArrayList(batch))
|
||||
}
|
||||
}
|
||||
|
||||
private data class SystemContactInfo(val name: String, val number: String, val id: Long)
|
||||
|
||||
private data class SignalContact(
|
||||
val id: Long,
|
||||
val supportsVoice: String?,
|
||||
val rawDisplayName: String?,
|
||||
val aggregateDisplayName: String?,
|
||||
val displayNameSource: Int
|
||||
) {
|
||||
fun isVoiceSupported(): Boolean {
|
||||
return "true" == supportsVoice
|
||||
}
|
||||
}
|
||||
|
||||
data class NameDetails(
|
||||
val displayName: String?,
|
||||
val givenName: String?,
|
||||
val familyName: String?,
|
||||
val prefix: String?,
|
||||
val suffix: String?,
|
||||
val middleName: String?
|
||||
)
|
||||
|
||||
data class PhoneDetails(
|
||||
val number: String?,
|
||||
val type: Int,
|
||||
val label: String?
|
||||
)
|
||||
|
||||
data class EmailDetails(
|
||||
val address: String?,
|
||||
val type: Int,
|
||||
val label: String?
|
||||
)
|
||||
|
||||
data class PostalAddressDetails(
|
||||
val type: Int,
|
||||
val label: String?,
|
||||
val street: String?,
|
||||
val poBox: String?,
|
||||
val neighborhood: String?,
|
||||
val city: String?,
|
||||
val region: String?,
|
||||
val postal: String?,
|
||||
val country: String?
|
||||
)
|
||||
}
|
|
@ -77,9 +77,7 @@ public class ContactShareEditActivity extends PassphraseRequiredActivity impleme
|
|||
ContactShareEditAdapter contactAdapter = new ContactShareEditAdapter(GlideApp.with(this), dynamicLanguage.getCurrentLocale(), this);
|
||||
contactList.setAdapter(contactAdapter);
|
||||
|
||||
SharedContactRepository contactRepository = new SharedContactRepository(this,
|
||||
AsyncTask.THREAD_POOL_EXECUTOR,
|
||||
SignalDatabase.contacts());
|
||||
SharedContactRepository contactRepository = new SharedContactRepository(this, AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
|
||||
viewModel = ViewModelProviders.of(this, new Factory(contactUris, contactRepository)).get(ContactShareEditViewModel.class);
|
||||
viewModel.getContacts().observe(this, contacts -> {
|
||||
|
|
|
@ -113,7 +113,7 @@ public final class ContactUtil {
|
|||
}
|
||||
}
|
||||
|
||||
public static @NonNull String getNormalizedPhoneNumber(@NonNull Context context, @NonNull String number) {
|
||||
public static @NonNull String getNormalizedPhoneNumber(@NonNull Context context, @Nullable String number) {
|
||||
return PhoneNumberFormatter.get(context).format(number);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package org.thoughtcrime.securesms.contactshare;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.ContactsContract;
|
||||
import android.text.TextUtils;
|
||||
|
@ -11,8 +10,10 @@ import androidx.annotation.Nullable;
|
|||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.contacts.ContactsDatabase;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
|
||||
import org.thoughtcrime.securesms.contacts.sync.SystemContactsRepository;
|
||||
import org.thoughtcrime.securesms.contacts.sync.SystemContactsRepository.NameDetails;
|
||||
import org.thoughtcrime.securesms.contacts.sync.SystemContactsRepository.PhoneDetails;
|
||||
import org.thoughtcrime.securesms.contactshare.Contact.Email;
|
||||
import org.thoughtcrime.securesms.contactshare.Contact.Name;
|
||||
import org.thoughtcrime.securesms.contactshare.Contact.Phone;
|
||||
|
@ -26,10 +27,11 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ezvcard.Ezvcard;
|
||||
import ezvcard.VCard;
|
||||
|
@ -40,17 +42,12 @@ public class SharedContactRepository {
|
|||
|
||||
private static final String TAG = Log.tag(SharedContactRepository.class);
|
||||
|
||||
private final Context context;
|
||||
private final Executor executor;
|
||||
private final ContactsDatabase contactsDatabase;
|
||||
private final Context context;
|
||||
private final Executor executor;
|
||||
|
||||
SharedContactRepository(@NonNull Context context,
|
||||
@NonNull Executor executor,
|
||||
@NonNull ContactsDatabase contactsDatabase)
|
||||
{
|
||||
this.context = context.getApplicationContext();
|
||||
this.executor = executor;
|
||||
this.contactsDatabase = contactsDatabase;
|
||||
SharedContactRepository(@NonNull Context context, @NonNull Executor executor) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
void getContacts(@NonNull List<Uri> contactUris, @NonNull ValueCallback<List<Contact>> callback) {
|
||||
|
@ -108,23 +105,16 @@ public class SharedContactRepository {
|
|||
|
||||
@WorkerThread
|
||||
private @Nullable Name getName(long contactId) {
|
||||
try (Cursor cursor = contactsDatabase.getNameDetails(contactId)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
String cursorDisplayName = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME));
|
||||
String cursorGivenName = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME));
|
||||
String cursorFamilyName = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME));
|
||||
String cursorPrefix = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.PREFIX));
|
||||
String cursorSuffix = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.SUFFIX));
|
||||
String cursorMiddleName = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME));
|
||||
NameDetails nameDetails = SystemContactsRepository.getNameDetails(context, contactId);
|
||||
|
||||
Name name = new Name(cursorDisplayName, cursorGivenName, cursorFamilyName, cursorPrefix, cursorSuffix, cursorMiddleName);
|
||||
if (!name.isEmpty()) {
|
||||
return name;
|
||||
}
|
||||
if (nameDetails != null) {
|
||||
Name name = new Name(nameDetails.getDisplayName(), nameDetails.getGivenName(), nameDetails.getFamilyName(), nameDetails.getPrefix(), nameDetails.getSuffix(), nameDetails.getMiddleName());
|
||||
if (!name.isEmpty()) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
String org = contactsDatabase.getOrganizationName(contactId);
|
||||
String org = SystemContactsRepository.getOrganizationName(context, contactId);
|
||||
if (!TextUtils.isEmpty(org)) {
|
||||
return new Name(org, org, null, null, null, null);
|
||||
}
|
||||
|
@ -134,20 +124,16 @@ public class SharedContactRepository {
|
|||
|
||||
@WorkerThread
|
||||
private @NonNull List<Phone> getPhoneNumbers(long contactId) {
|
||||
Map<String, Phone> numberMap = new HashMap<>();
|
||||
try (Cursor cursor = contactsDatabase.getPhoneDetails(contactId)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
String cursorNumber = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER));
|
||||
int cursorType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.TYPE));
|
||||
String cursorLabel = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.LABEL));
|
||||
Map<String, Phone> numberMap = new HashMap<>();
|
||||
List<PhoneDetails> phoneDetails = SystemContactsRepository.getPhoneDetails(context, contactId);
|
||||
|
||||
String number = ContactUtil.getNormalizedPhoneNumber(context, cursorNumber);
|
||||
Phone existing = numberMap.get(number);
|
||||
Phone candidate = new Phone(number, VCardUtil.phoneTypeFromContactType(cursorType), cursorLabel);
|
||||
for (PhoneDetails phone : phoneDetails) {
|
||||
String number = ContactUtil.getNormalizedPhoneNumber(context, phone.getNumber());
|
||||
Phone existing = numberMap.get(number);
|
||||
Phone candidate = new Phone(number, VCardUtil.phoneTypeFromContactType(phone.getType()), phone.getLabel());
|
||||
|
||||
if (existing == null || (existing.getType() == Phone.Type.CUSTOM && existing.getLabel() == null)) {
|
||||
numberMap.put(number, candidate);
|
||||
}
|
||||
if (existing == null || (existing.getType() == Phone.Type.CUSTOM && existing.getLabel() == null)) {
|
||||
numberMap.put(number, candidate);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,50 +144,31 @@ public class SharedContactRepository {
|
|||
|
||||
@WorkerThread
|
||||
private @NonNull List<Email> getEmails(long contactId) {
|
||||
List<Email> emails = new LinkedList<>();
|
||||
|
||||
try (Cursor cursor = contactsDatabase.getEmailDetails(contactId)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
String cursorEmail = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Email.ADDRESS));
|
||||
int cursorType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Email.TYPE));
|
||||
String cursorLabel = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Email.LABEL));
|
||||
|
||||
emails.add(new Email(cursorEmail, VCardUtil.emailTypeFromContactType(cursorType), cursorLabel));
|
||||
}
|
||||
}
|
||||
|
||||
return emails;
|
||||
return SystemContactsRepository.getEmailDetails(context, contactId)
|
||||
.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(email -> new Email(Objects.requireNonNull(email.getAddress()),
|
||||
VCardUtil.emailTypeFromContactType(email.getType()),
|
||||
email.getLabel()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private @NonNull List<PostalAddress> getPostalAddresses(long contactId) {
|
||||
List<PostalAddress> postalAddresses = new LinkedList<>();
|
||||
|
||||
try (Cursor cursor = contactsDatabase.getPostalAddressDetails(contactId)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
int cursorType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.TYPE));
|
||||
String cursorLabel = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.LABEL));
|
||||
String cursorStreet = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.STREET));
|
||||
String cursorPoBox = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.POBOX));
|
||||
String cursorNeighborhood = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.NEIGHBORHOOD));
|
||||
String cursorCity = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.CITY));
|
||||
String cursorRegion = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.REGION));
|
||||
String cursorPostal = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE));
|
||||
String cursorCountry = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY));
|
||||
|
||||
postalAddresses.add(new PostalAddress(VCardUtil.postalAddressTypeFromContactType(cursorType),
|
||||
cursorLabel,
|
||||
cursorStreet,
|
||||
cursorPoBox,
|
||||
cursorNeighborhood,
|
||||
cursorCity,
|
||||
cursorRegion,
|
||||
cursorPostal,
|
||||
cursorCountry));
|
||||
}
|
||||
}
|
||||
|
||||
return postalAddresses;
|
||||
return SystemContactsRepository.getPostalAddressDetails(context, contactId)
|
||||
.stream()
|
||||
.map(address -> {
|
||||
return new PostalAddress(VCardUtil.postalAddressTypeFromContactType(address.getType()),
|
||||
address.getLabel(),
|
||||
address.getStreet(),
|
||||
address.getPoBox(),
|
||||
address.getNeighborhood(),
|
||||
address.getCity(),
|
||||
address.getRegion(),
|
||||
address.getPostal(),
|
||||
address.getCountry());
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
|
@ -223,7 +190,7 @@ public class SharedContactRepository {
|
|||
|
||||
@WorkerThread
|
||||
private @Nullable AvatarInfo getSystemAvatarInfo(long contactId) {
|
||||
Uri uri = contactsDatabase.getAvatarUri(contactId);
|
||||
Uri uri = SystemContactsRepository.getAvatarUri(context, contactId);
|
||||
if (uri != null) {
|
||||
return new AvatarInfo(uri, false);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import android.app.Application
|
|||
import android.content.Context
|
||||
import net.zetetic.database.sqlcipher.SQLiteOpenHelper
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.contacts.ContactsDatabase
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSecret
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret
|
||||
|
@ -49,7 +48,6 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
|
|||
val pushDatabase: PushDatabase = PushDatabase(context, this)
|
||||
val groupDatabase: GroupDatabase = GroupDatabase(context, this)
|
||||
val recipientDatabase: RecipientDatabase = RecipientDatabase(context, this)
|
||||
val contactsDatabase: ContactsDatabase = ContactsDatabase(context)
|
||||
val groupReceiptDatabase: GroupReceiptDatabase = GroupReceiptDatabase(context, this)
|
||||
val preKeyDatabase: OneTimePreKeyDatabase = OneTimePreKeyDatabase(context, this)
|
||||
val signedPreKeyDatabase: SignedPreKeyDatabase = SignedPreKeyDatabase(context, this)
|
||||
|
@ -332,11 +330,6 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
|
|||
val chatColors: ChatColorsDatabase
|
||||
get() = instance!!.chatColorsDatabase
|
||||
|
||||
@get:JvmStatic
|
||||
@get:JvmName("contacts")
|
||||
val contacts: ContactsDatabase
|
||||
get() = instance!!.contactsDatabase
|
||||
|
||||
@get:JvmStatic
|
||||
@get:JvmName("distributionLists")
|
||||
val distributionLists: DistributionListDatabase
|
||||
|
|
Ładowanie…
Reference in New Issue