Added ability to share contacts.

The "contact" option in the attachments tray now brings you through an
optimized contact sharing flow, allowing you to select specific fields
to share. The contact is then presented as a special message type,
allowing you to interact with the card to add the contact to your system
contacts, invite them to signal, initiate a signal message, etc.
This commit is contained in:
Greyson Parrelli
2018-04-26 17:03:54 -07:00
parent 17dbdbd0a9
commit 54dbffaf30
90 changed files with 3628 additions and 195 deletions

View File

@@ -19,6 +19,8 @@ package org.thoughtcrime.securesms.contacts;
import android.accounts.Account;
import android.annotation.SuppressLint;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentUris;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
@@ -226,6 +228,114 @@ public class ContactsDatabase {
}
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 Address address, long rawContactId)
{

View File

@@ -2,6 +2,9 @@ package org.thoughtcrime.securesms.contacts.avatars;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.bumptech.glide.load.Key;
@@ -13,4 +16,8 @@ public interface ContactPhoto extends Key {
InputStream openInputStream(Context context) throws IOException;
@Nullable Uri getUri(@NonNull Context context);
boolean isProfilePhoto();
}

View File

@@ -2,7 +2,9 @@ package org.thoughtcrime.securesms.contacts.avatars;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
@@ -37,6 +39,16 @@ public class GroupRecordContactPhoto implements ContactPhoto {
throw new IOException("Couldn't load avatar for group: " + address.toGroupString());
}
@Override
public @Nullable Uri getUri(@NonNull Context context) {
return null;
}
@Override
public boolean isProfilePhoto() {
return false;
}
@Override
public void updateDiskCacheKey(MessageDigest messageDigest) {
messageDigest.update(address.serialize().getBytes());

View File

@@ -2,7 +2,9 @@ package org.thoughtcrime.securesms.contacts.avatars;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
@@ -27,6 +29,16 @@ public class ProfileContactPhoto implements ContactPhoto {
return AvatarHelper.getInputStreamFor(context, address);
}
@Override
public @Nullable Uri getUri(@NonNull Context context) {
return Uri.fromFile(AvatarHelper.getAvatarFile(context, address));
}
@Override
public boolean isProfilePhoto() {
return true;
}
@Override
public void updateDiskCacheKey(MessageDigest messageDigest) {
messageDigest.update(address.serialize().getBytes());

View File

@@ -4,6 +4,7 @@ package org.thoughtcrime.securesms.contacts.avatars;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.util.Conversions;
@@ -27,7 +28,17 @@ public class SystemContactPhoto implements ContactPhoto {
@Override
public InputStream openInputStream(Context context) throws FileNotFoundException {
return context.getContentResolver().openInputStream(contactPhotoUri);
}
@Nullable
@Override
public Uri getUri(@NonNull Context context) {
return contactPhotoUri;
}
@Override
public boolean isProfilePhoto() {
return false;
}
@Override