Signal-Android/app/src/main/java/org/thoughtcrime/securesms/contactshare/SharedContactRepository.java

239 wiersze
8.3 KiB
Java

package org.thoughtcrime.securesms.contactshare;
import android.content.Context;
import android.net.Uri;
import android.provider.ContactsContract;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
import org.signal.contacts.SystemContactsRepository;
import org.signal.contacts.SystemContactsRepository.NameDetails;
import org.signal.contacts.SystemContactsRepository.PhoneDetails;
import org.thoughtcrime.securesms.contactshare.Contact.Email;
import org.thoughtcrime.securesms.contactshare.Contact.Name;
import org.thoughtcrime.securesms.contactshare.Contact.Phone;
import org.thoughtcrime.securesms.contactshare.Contact.PostalAddress;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.recipients.Recipient;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
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;
import static org.thoughtcrime.securesms.contactshare.Contact.Avatar;
public class SharedContactRepository {
private static final String TAG = Log.tag(SharedContactRepository.class);
private final Context context;
private final Executor executor;
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) {
executor.execute(() -> {
List<Contact> contacts = new ArrayList<>(contactUris.size());
for (Uri contactUri : contactUris) {
Contact contact;
if (ContactsContract.AUTHORITY.equals(contactUri.getAuthority())) {
contact = getContactFromSystemContacts(ContactUtil.getContactIdFromUri(contactUri));
} else {
contact = getContactFromVcard(contactUri);
}
if (contact != null) {
contacts.add(contact);
}
}
callback.onComplete(contacts);
});
}
@WorkerThread
private @Nullable Contact getContactFromSystemContacts(long contactId) {
Name name = getName(contactId);
if (name == null) {
Log.w(TAG, "Couldn't find a name associated with the provided contact ID.");
return null;
}
List<Phone> phoneNumbers = getPhoneNumbers(contactId);
AvatarInfo avatarInfo = getAvatarInfo(contactId, phoneNumbers);
Avatar avatar = avatarInfo != null ? new Avatar(avatarInfo.uri, avatarInfo.isProfile) : null;
return new Contact(name, null, phoneNumbers, getEmails(contactId), getPostalAddresses(contactId), avatar);
}
@WorkerThread
private @Nullable Contact getContactFromVcard(@NonNull Uri uri) {
Contact contact = null;
try (InputStream stream = PartAuthority.getAttachmentStream(context, uri)) {
VCard vcard = Ezvcard.parse(stream).first();
contact = VCardUtil.getContactFromVcard(vcard);
} catch (IOException e) {
Log.w(TAG, "Failed to parse the vcard.", e);
}
if (BlobProvider.AUTHORITY.equals(uri.getAuthority())) {
BlobProvider.getInstance().delete(context, uri);
}
return contact;
}
@WorkerThread
private @Nullable Name getName(long contactId) {
NameDetails nameDetails = SystemContactsRepository.getNameDetails(context, contactId);
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 = SystemContactsRepository.getOrganizationName(context, contactId);
if (!TextUtils.isEmpty(org)) {
return new Name(org, org, null, null, null, null);
}
return null;
}
@WorkerThread
private @NonNull List<Phone> getPhoneNumbers(long contactId) {
Map<String, Phone> numberMap = new HashMap<>();
List<PhoneDetails> phoneDetails = SystemContactsRepository.getPhoneDetails(context, contactId);
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);
}
}
List<Phone> numbers = new ArrayList<>(numberMap.size());
numbers.addAll(numberMap.values());
return numbers;
}
@WorkerThread
private @NonNull List<Email> getEmails(long contactId) {
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) {
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
private @Nullable AvatarInfo getAvatarInfo(long contactId, List<Phone> phoneNumbers) {
AvatarInfo systemAvatar = getSystemAvatarInfo(contactId);
if (systemAvatar != null) {
return systemAvatar;
}
for (Phone phoneNumber : phoneNumbers) {
AvatarInfo recipientAvatar = getRecipientAvatarInfo(PhoneNumberFormatter.get(context).format(phoneNumber.getNumber()));
if (recipientAvatar != null) {
return recipientAvatar;
}
}
return null;
}
@WorkerThread
private @Nullable AvatarInfo getSystemAvatarInfo(long contactId) {
Uri uri = SystemContactsRepository.getAvatarUri(context, contactId);
if (uri != null) {
return new AvatarInfo(uri, false);
}
return null;
}
@WorkerThread
private @Nullable AvatarInfo getRecipientAvatarInfo(String address) {
Recipient recipient = Recipient.external(context, address);
ContactPhoto contactPhoto = recipient.getContactPhoto();
if (contactPhoto != null) {
Uri avatarUri = contactPhoto.getUri(context);
if (avatarUri != null) {
return new AvatarInfo(avatarUri, contactPhoto.isProfilePhoto());
}
}
return null;
}
interface ValueCallback<T> {
void onComplete(@NonNull T value);
}
private static class AvatarInfo {
private final Uri uri;
private final boolean isProfile;
private AvatarInfo(Uri uri, boolean isProfile) {
this.uri = uri;
this.isProfile = isProfile;
}
public Uri getUri() {
return uri;
}
public boolean isProfile() {
return isProfile;
}
}
}