diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactAccessor.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactAccessor.java
index aa104dc4d..81eca1d7b 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactAccessor.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactAccessor.java
@@ -23,28 +23,37 @@ import android.database.MergeCursor;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.PhoneLookup;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
+import androidx.annotation.NonNull;
+
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.contactshare.Contact;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
+import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.util.CursorUtil;
+import org.thoughtcrime.securesms.util.SqlUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
@@ -65,6 +74,9 @@ public class ContactAccessor {
public static final String PUSH_COLUMN = "push";
+ private static final String GIVEN_NAME = ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME;
+ private static final String FAMILY_NAME = ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME;
+
private static final ContactAccessor instance = new ContactAccessor();
public static synchronized ContactAccessor getInstance() {
@@ -85,55 +97,42 @@ public class ContactAccessor {
return results;
}
+ /**
+ * Gets and returns a cursor of data for all contacts, containing both phone number data and
+ * structured name data.
+ *
+ * Cursor rows are ordered as follows:
+ *
+ *
+ * - Contact Lookup Key
+ * - Mimetype
+ * - id
+ *
+ *
+ * The lookup key is a fixed value that allows you to verify two rows in the database actually
+ * belong to the same contact, since the contact uri can be unstable (if a sync fails, say.)
+ *
+ * We order by id explicitly here for the same contact sync failure error, which could result in
+ * multiple structured name rows for the same user. By ordering by id DESC, we ensure we get
+ * whatever the latest input data was.
+ *
+ * What this results in is a cursor that looks like:
+ *
+ * Alice phone 1
+ * Alice phone 2
+ * Alice structured name 2
+ * Alice structured name 1
+ * Bob phone 1
+ * ... etc.
+ */
public Cursor getAllSystemContacts(Context context) {
- return context.getContentResolver().query(Phone.CONTENT_URI, new String[] {Phone.NUMBER, Phone.DISPLAY_NAME, Phone.LABEL, Phone.PHOTO_URI, Phone._ID, Phone.LOOKUP_KEY, Phone.TYPE}, null, null, null);
- }
+ Uri uri = ContactsContract.Data.CONTENT_URI;
+ String[] projection = SqlUtil.buildArgs(ContactsContract.Data.MIMETYPE, Phone.NUMBER, Phone.DISPLAY_NAME, Phone.LABEL, Phone.PHOTO_URI, Phone._ID, Phone.LOOKUP_KEY, Phone.TYPE, GIVEN_NAME, FAMILY_NAME);
+ String where = ContactsContract.Data.MIMETYPE + " IN (?, ?)";
+ String[] args = SqlUtil.buildArgs(Phone.CONTENT_ITEM_TYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
+ String orderBy = Phone.LOOKUP_KEY + " ASC, " + ContactsContract.Data.MIMETYPE + " DESC, " + ContactsContract.CommonDataKinds.Phone._ID + " DESC";
- public boolean isSystemContact(Context context, String number) {
- Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
- String[] projection = new String[]{PhoneLookup.DISPLAY_NAME, PhoneLookup.LOOKUP_KEY,
- PhoneLookup._ID, PhoneLookup.NUMBER};
- Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);
-
- try {
- if (cursor != null && cursor.moveToFirst()) {
- return true;
- }
- } finally {
- if (cursor != null) cursor.close();
- }
-
- return false;
- }
-
- public Collection getContactsWithPush(Context context) {
- final ContentResolver resolver = context.getContentResolver();
- final String[] inProjection = new String[]{PhoneLookup._ID, PhoneLookup.DISPLAY_NAME};
-
- final List registeredAddresses = Stream.of(DatabaseFactory.getRecipientDatabase(context).getRegistered())
- .map(Recipient::resolved)
- .filter(r -> r.getE164().isPresent())
- .map(Recipient::requireE164)
- .toList();
- final Collection lookupData = new ArrayList<>(registeredAddresses.size());
-
- for (String registeredAddress : registeredAddresses) {
- Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(registeredAddress));
- Cursor lookupCursor = resolver.query(uri, inProjection, null, null, null);
-
- try {
- if (lookupCursor != null && lookupCursor.moveToFirst()) {
- final ContactData contactData = new ContactData(lookupCursor.getLong(0), lookupCursor.getString(1));
- contactData.numbers.add(new NumberData("TextSecure", registeredAddress));
- lookupData.add(contactData);
- }
- } finally {
- if (lookupCursor != null)
- lookupCursor.close();
- }
- }
-
- return lookupData;
+ return context.getContentResolver().query(uri, projection, where, args, orderBy);
}
public String getNameFromContact(Context context, Uri uri) {
@@ -160,13 +159,13 @@ public class ContactAccessor {
private ContactData getContactData(Context context, String displayName, long id) {
ContactData contactData = new ContactData(id, displayName);
- Cursor numberCursor = null;
-
- try {
- numberCursor = context.getContentResolver().query(Phone.CONTENT_URI, null,
- Phone.CONTACT_ID + " = ?",
- new String[] {contactData.id + ""}, null);
+ try (Cursor numberCursor = context.getContentResolver().query(Phone.CONTENT_URI,
+ null,
+ Phone.CONTACT_ID + " = ?",
+ new String[] {contactData.id + ""},
+ null))
+ {
while (numberCursor != null && numberCursor.moveToNext()) {
int type = numberCursor.getInt(numberCursor.getColumnIndexOrThrow(Phone.TYPE));
String label = numberCursor.getString(numberCursor.getColumnIndexOrThrow(Phone.LABEL));
@@ -175,9 +174,6 @@ public class ContactAccessor {
contactData.numbers.add(new NumberData(typeLabel, number));
}
- } finally {
- if (numberCursor != null)
- numberCursor.close();
}
return contactData;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactRepository.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactRepository.java
index 3696efdc1..7c614b138 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactRepository.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactRepository.java
@@ -57,7 +57,7 @@ public class ContactRepository {
add(new Pair<>(ID_COLUMN, cursor -> CursorUtil.requireLong(cursor, RecipientDatabase.ID)));
add(new Pair<>(NAME_COLUMN, cursor -> {
- String system = CursorUtil.requireString(cursor, RecipientDatabase.SYSTEM_DISPLAY_NAME);
+ String system = CursorUtil.requireString(cursor, RecipientDatabase.SYSTEM_JOINED_NAME);
String profile = CursorUtil.requireString(cursor, RecipientDatabase.SEARCH_PROFILE_NAME);
return Util.getFirstNonEmpty(system, profile);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactHolder.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactHolder.java
new file mode 100644
index 000000000..4a8405252
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactHolder.java
@@ -0,0 +1,53 @@
+package org.thoughtcrime.securesms.contacts.sync;
+
+import androidx.annotation.NonNull;
+
+import org.thoughtcrime.securesms.database.RecipientDatabase;
+import org.thoughtcrime.securesms.profiles.ProfileName;
+
+import java.util.LinkedList;
+import java.util.List;
+
+final class ContactHolder {
+
+ private final String lookupKey;
+ private final List phoneNumberRecords = new LinkedList<>();
+
+ private StructuredNameRecord structuredNameRecord;
+
+ ContactHolder(@NonNull String lookupKey) {
+ this.lookupKey = lookupKey;
+ }
+
+ @NonNull String getLookupKey() {
+ return lookupKey;
+ }
+
+ public void addPhoneNumberRecord(@NonNull PhoneNumberRecord phoneNumberRecord) {
+ phoneNumberRecords.add(phoneNumberRecord);
+ }
+
+ public void setStructuredNameRecord(@NonNull StructuredNameRecord structuredNameRecord) {
+ this.structuredNameRecord = structuredNameRecord;
+ }
+
+ void commit(@NonNull RecipientDatabase.BulkOperationsHandle handle) {
+ for (PhoneNumberRecord phoneNumberRecord : phoneNumberRecords) {
+ handle.setSystemContactInfo(phoneNumberRecord.getRecipientId(),
+ getProfileName(phoneNumberRecord.getDisplayName()),
+ phoneNumberRecord.getContactPhotoUri(),
+ phoneNumberRecord.getContactLabel(),
+ phoneNumberRecord.getPhoneType(),
+ phoneNumberRecord.getContactUri().toString());
+ }
+ }
+
+ private @NonNull ProfileName getProfileName(@NonNull String displayName) {
+ if (structuredNameRecord.hasGivenName()) {
+ return structuredNameRecord.asProfileName();
+ } else {
+ return ProfileName.asGiven(displayName);
+ }
+ }
+
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/DirectoryHelper.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/DirectoryHelper.java
index dbf701595..128775eee 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/DirectoryHelper.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/DirectoryHelper.java
@@ -7,7 +7,6 @@ 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.ContactsContract;
import android.text.TextUtils;
@@ -43,6 +42,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.registration.RegistrationUtil;
import org.thoughtcrime.securesms.sms.IncomingJoinedMessage;
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
+import org.thoughtcrime.securesms.util.CursorUtil;
import org.thoughtcrime.securesms.util.ProfileUtil;
import org.thoughtcrime.securesms.util.SetUtil;
import org.thoughtcrime.securesms.util.Stopwatch;
@@ -63,6 +63,7 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
@@ -320,27 +321,63 @@ public class DirectoryHelper {
contactsDatabase.removeDeletedRawContacts(account.getAccount());
contactsDatabase.setRegisteredUsers(account.getAccount(), activeAddresses, removeMissing);
- Cursor cursor = ContactAccessor.getInstance().getAllSystemContacts(context);
- BulkOperationsHandle handle = recipientDatabase.beginBulkSystemContactUpdate();
+ BulkOperationsHandle handle = recipientDatabase.beginBulkSystemContactUpdate();
- try {
+ ContactHolder old = null;
+ try (Cursor cursor = ContactAccessor.getInstance().getAllSystemContacts(context)) {
while (cursor != null && cursor.moveToNext()) {
- String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER));
+ String lookupKey = getLookupKey(cursor);
+ String mimeType = getMimeType(cursor);
+ ContactHolder contactHolder = new ContactHolder(lookupKey);
- if (isValidContactNumber(number)) {
- String formattedNumber = PhoneNumberFormatter.get(context).format(number);
- String realNumber = Util.getFirstNonEmpty(rewrites.get(formattedNumber), formattedNumber);
- RecipientId recipientId = Recipient.externalContact(context, realNumber).getId();
- String displayName = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
- String contactPhotoUri = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.PHOTO_URI));
- String contactLabel = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.LABEL));
- int phoneType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.TYPE));
- Uri contactUri = ContactsContract.Contacts.getLookupUri(cursor.getLong(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone._ID)),
- cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY)));
-
- handle.setSystemContactInfo(recipientId, displayName, contactPhotoUri, contactLabel, phoneType, contactUri.toString());
+ if (!isPhoneMimeType(mimeType)) {
+ Log.w(TAG, "Ignoring unexpected mime type: " + mimeType);
}
+
+ while (getLookupKey(cursor).equals(lookupKey) && isPhoneMimeType(getMimeType(cursor))) {
+ String number = CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.Phone.NUMBER);
+
+ if (isValidContactNumber(number)) {
+ String formattedNumber = PhoneNumberFormatter.get(context).format(number);
+ String realNumber = Util.getFirstNonEmpty(rewrites.get(formattedNumber), formattedNumber);
+
+ PhoneNumberRecord.Builder builder = new PhoneNumberRecord.Builder();
+
+ builder.withRecipientId(Recipient.externalContact(context, realNumber).getId());
+ builder.withDisplayName(CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
+ builder.withContactPhotoUri(CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.Phone.PHOTO_URI));
+ builder.withContactLabel(CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.Phone.LABEL));
+ builder.withPhoneType(CursorUtil.requireInt(cursor, ContactsContract.CommonDataKinds.Phone.TYPE));
+ builder.withContactUri(ContactsContract.Contacts.getLookupUri(CursorUtil.requireLong(cursor, ContactsContract.CommonDataKinds.Phone._ID),
+ CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY)));
+
+ contactHolder.addPhoneNumberRecord(builder.build());
+ } else {
+ Log.w(TAG, "Skipping phone entry with invalid number");
+ }
+
+ cursor.moveToNext();
+ }
+
+ if (getLookupKey(cursor).equals(lookupKey)) {
+ if (isStructuredNameMimeType(getMimeType(cursor))) {
+ StructuredNameRecord.Builder builder = new StructuredNameRecord.Builder();
+
+ builder.withGivenName(CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME));
+ builder.withFamilyName(CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME));
+
+ contactHolder.setStructuredNameRecord(builder.build());
+ } else {
+ Log.i(TAG, "Skipping invalid mimeType " + mimeType);
+ }
+ } else {
+ Log.i(TAG, "No structured name for user, rolling back cursor.");
+ cursor.moveToPrevious();
+ }
+
+ contactHolder.commit(handle);
}
+
} finally {
handle.finish();
}
@@ -358,10 +395,26 @@ public class DirectoryHelper {
}
}
+ private static boolean isPhoneMimeType(@NonNull String mimeType) {
+ return ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE.equals(mimeType);
+ }
+
+ private static boolean isStructuredNameMimeType(@NonNull String mimeType) {
+ return ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE.equals(mimeType);
+ }
+
private static boolean isValidContactNumber(@Nullable String number) {
return !TextUtils.isEmpty(number) && !UuidUtil.isUuid(number);
}
+ private static @NonNull String getLookupKey(@NonNull Cursor cursor) {
+ return Objects.requireNonNull(CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY));
+ }
+
+ private static @NonNull String getMimeType(@NonNull Cursor cursor) {
+ 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);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/PhoneNumberRecord.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/PhoneNumberRecord.java
new file mode 100644
index 000000000..0393f8941
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/PhoneNumberRecord.java
@@ -0,0 +1,99 @@
+package org.thoughtcrime.securesms.contacts.sync;
+
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.thoughtcrime.securesms.recipients.RecipientId;
+
+import java.util.Objects;
+
+/**
+ * Represents all the data we pull from a Phone data cursor row from the contacts database.
+ */
+final class PhoneNumberRecord {
+
+ private final RecipientId recipientId;
+ private final String displayName;
+ private final String contactPhotoUri;
+ private final String contactLabel;
+ private final int phoneType;
+ private final Uri contactUri;
+
+ private PhoneNumberRecord(@NonNull PhoneNumberRecord.Builder builder) {
+ recipientId = Objects.requireNonNull(builder.recipientId);
+ displayName = builder.displayName;
+ contactPhotoUri = builder.contactPhotoUri;
+ contactLabel = builder.contactLabel;
+ phoneType = builder.phoneType;
+ contactUri = builder.contactUri;
+ }
+
+ @NonNull RecipientId getRecipientId() {
+ return recipientId;
+ }
+
+ @Nullable String getDisplayName() {
+ return displayName;
+ }
+
+ @Nullable String getContactPhotoUri() {
+ return contactPhotoUri;
+ }
+
+ @Nullable String getContactLabel() {
+ return contactLabel;
+ }
+
+ int getPhoneType() {
+ return phoneType;
+ }
+
+ @Nullable Uri getContactUri() {
+ return contactUri;
+ }
+
+ final static class Builder {
+ private RecipientId recipientId;
+ private String displayName;
+ private String contactPhotoUri;
+ private String contactLabel;
+ private int phoneType;
+ private Uri contactUri;
+
+ @NonNull Builder withRecipientId(@NonNull RecipientId recipientId) {
+ this.recipientId = recipientId;
+ return this;
+ }
+
+ @NonNull Builder withDisplayName(@Nullable String displayName) {
+ this.displayName = displayName;
+ return this;
+ }
+
+ @NonNull Builder withContactUri(@Nullable Uri contactUri) {
+ this.contactUri = contactUri;
+ return this;
+ }
+
+ @NonNull Builder withContactLabel(@NonNull String contactLabel) {
+ this.contactLabel = contactLabel;
+ return this;
+ }
+
+ @NonNull Builder withContactPhotoUri(@NonNull String contactPhotoUri) {
+ this.contactPhotoUri = contactPhotoUri;
+ return this;
+ }
+
+ @NonNull Builder withPhoneType(int phoneType) {
+ this.phoneType = phoneType;
+ return this;
+ }
+
+ @NonNull PhoneNumberRecord build() {
+ return new PhoneNumberRecord(this);
+ }
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/StructuredNameRecord.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/StructuredNameRecord.java
new file mode 100644
index 000000000..bfcacd2a7
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/StructuredNameRecord.java
@@ -0,0 +1,46 @@
+package org.thoughtcrime.securesms.contacts.sync;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.thoughtcrime.securesms.profiles.ProfileName;
+
+/**
+ * Represents the data pulled from a StructuredName row of a Contacts data cursor.
+ */
+final class StructuredNameRecord {
+ private final String givenName;
+ private final String familyName;
+
+ StructuredNameRecord(@NonNull StructuredNameRecord.Builder builder) {
+ this.givenName = builder.givenName;
+ this.familyName = builder.familyName;
+ }
+
+ public boolean hasGivenName() {
+ return givenName != null;
+ }
+
+ public @NonNull ProfileName asProfileName() {
+ return ProfileName.fromParts(givenName, familyName);
+ }
+
+ final static class Builder {
+ private String givenName;
+ private String familyName;
+
+ @NonNull Builder withGivenName(@Nullable String givenName) {
+ this.givenName = givenName;
+ return this;
+ }
+
+ @NonNull Builder withFamilyName(@Nullable String familyName) {
+ this.familyName = familyName;
+ return this;
+ }
+
+ @NonNull StructuredNameRecord build() {
+ return new StructuredNameRecord(this);
+ }
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java
index 7a9a80615..b63de248e 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java
@@ -39,7 +39,6 @@ import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor;
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
-import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -113,7 +112,9 @@ public class RecipientDatabase extends Database {
private static final String DEFAULT_SUBSCRIPTION_ID = "default_subscription_id";
private static final String MESSAGE_EXPIRATION_TIME = "message_expiration_time";
public static final String REGISTERED = "registered";
- public static final String SYSTEM_DISPLAY_NAME = "system_display_name";
+ public static final String SYSTEM_JOINED_NAME = "system_display_name";
+ public static final String SYSTEM_FAMILY_NAME = "system_family_name";
+ public static final String SYSTEM_GIVEN_NAME = "system_given_name";
private static final String SYSTEM_PHOTO_URI = "system_photo_uri";
public static final String SYSTEM_PHONE_TYPE = "system_phone_type";
public static final String SYSTEM_PHONE_LABEL = "system_phone_label";
@@ -157,7 +158,7 @@ public class RecipientDatabase extends Database {
ID, UUID, USERNAME, PHONE, EMAIL, GROUP_ID, GROUP_TYPE,
BLOCKED, MESSAGE_RINGTONE, CALL_RINGTONE, MESSAGE_VIBRATE, CALL_VIBRATE, MUTE_UNTIL, COLOR, SEEN_INVITE_REMINDER, DEFAULT_SUBSCRIPTION_ID, MESSAGE_EXPIRATION_TIME, REGISTERED,
PROFILE_KEY, PROFILE_KEY_CREDENTIAL,
- SYSTEM_DISPLAY_NAME, SYSTEM_PHOTO_URI, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, SYSTEM_CONTACT_URI,
+ SYSTEM_GIVEN_NAME, SYSTEM_FAMILY_NAME, SYSTEM_PHOTO_URI, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, SYSTEM_CONTACT_URI,
PROFILE_GIVEN_NAME, PROFILE_FAMILY_NAME, SIGNAL_PROFILE_AVATAR, PROFILE_SHARING, LAST_PROFILE_FETCH,
NOTIFICATION_CHANNEL,
UNIDENTIFIED_ACCESS_MODE,
@@ -170,15 +171,15 @@ public class RecipientDatabase extends Database {
};
private static final String[] ID_PROJECTION = new String[]{ID};
- private static final String[] SEARCH_PROJECTION = new String[]{ID, SYSTEM_DISPLAY_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, ABOUT, ABOUT_EMOJI, "COALESCE(" + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ") AS " + SEARCH_PROFILE_NAME, "COALESCE(" + nullIfEmpty(SYSTEM_DISPLAY_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ") AS " + SORT_NAME};
- public static final String[] SEARCH_PROJECTION_NAMES = new String[]{ID, SYSTEM_DISPLAY_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, ABOUT, ABOUT_EMOJI, SEARCH_PROFILE_NAME, SORT_NAME};
+ private static final String[] SEARCH_PROJECTION = new String[]{ID, SYSTEM_JOINED_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, ABOUT, ABOUT_EMOJI, "COALESCE(" + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ") AS " + SEARCH_PROFILE_NAME, "COALESCE(" + nullIfEmpty(SYSTEM_JOINED_NAME) + ", " + nullIfEmpty(SYSTEM_GIVEN_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ") AS " + SORT_NAME};
+ public static final String[] SEARCH_PROJECTION_NAMES = new String[]{ID, SYSTEM_JOINED_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, ABOUT, ABOUT_EMOJI, SEARCH_PROFILE_NAME, SORT_NAME};
private static final String[] TYPED_RECIPIENT_PROJECTION = Stream.of(RECIPIENT_PROJECTION)
.map(columnName -> TABLE_NAME + "." + columnName)
.toList().toArray(new String[0]);
static final String[] TYPED_RECIPIENT_PROJECTION_NO_ID = Arrays.copyOfRange(TYPED_RECIPIENT_PROJECTION, 1, TYPED_RECIPIENT_PROJECTION.length);
- private static final String[] MENTION_SEARCH_PROJECTION = new String[]{ID, removeWhitespace("COALESCE(" + nullIfEmpty(SYSTEM_DISPLAY_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ", " + nullIfEmpty(PHONE) + ")") + " AS " + SORT_NAME};
+ private static final String[] MENTION_SEARCH_PROJECTION = new String[]{ID, removeWhitespace("COALESCE(" + nullIfEmpty(SYSTEM_JOINED_NAME) + ", " + nullIfEmpty(SYSTEM_GIVEN_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ", " + nullIfEmpty(PHONE) + ")") + " AS " + SORT_NAME};
public static final String[] CREATE_INDEXS = new String[] {
"CREATE INDEX IF NOT EXISTS recipient_dirty_index ON " + TABLE_NAME + " (" + DIRTY + ");",
@@ -339,7 +340,9 @@ public class RecipientDatabase extends Database {
DEFAULT_SUBSCRIPTION_ID + " INTEGER DEFAULT -1, " +
MESSAGE_EXPIRATION_TIME + " INTEGER DEFAULT 0, " +
REGISTERED + " INTEGER DEFAULT " + RegisteredState.UNKNOWN.getId() + ", " +
- SYSTEM_DISPLAY_NAME + " TEXT DEFAULT NULL, " +
+ SYSTEM_GIVEN_NAME + " TEXT DEFAULT NULL, " +
+ SYSTEM_FAMILY_NAME + " TEXT DEFAULT NULL, " +
+ SYSTEM_JOINED_NAME + " TEXT DEFAULT NULL, " +
SYSTEM_PHOTO_URI + " TEXT DEFAULT NULL, " +
SYSTEM_PHONE_LABEL + " TEXT DEFAULT NULL, " +
SYSTEM_PHONE_TYPE + " INTEGER DEFAULT -1, " +
@@ -1264,7 +1267,8 @@ public class RecipientDatabase extends Database {
int registeredState = CursorUtil.requireInt(cursor, REGISTERED);
String profileKeyString = CursorUtil.requireString(cursor, PROFILE_KEY);
String profileKeyCredentialString = CursorUtil.requireString(cursor, PROFILE_KEY_CREDENTIAL);
- String systemDisplayName = CursorUtil.requireString(cursor, SYSTEM_DISPLAY_NAME);
+ String systemGivenName = CursorUtil.requireString(cursor, SYSTEM_GIVEN_NAME);
+ String systemFamilyName = CursorUtil.requireString(cursor, SYSTEM_FAMILY_NAME);
String systemContactPhoto = CursorUtil.requireString(cursor, SYSTEM_PHOTO_URI);
String systemPhoneLabel = CursorUtil.requireString(cursor, SYSTEM_PHONE_LABEL);
String systemContactUri = CursorUtil.requireString(cursor, SYSTEM_CONTACT_URI);
@@ -1350,7 +1354,7 @@ public class RecipientDatabase extends Database {
RegisteredState.fromId(registeredState),
profileKey,
profileKeyCredential,
- systemDisplayName,
+ ProfileName.fromParts(systemGivenName, systemFamilyName),
systemContactPhoto,
systemPhoneLabel,
systemContactUri,
@@ -1743,7 +1747,7 @@ public class RecipientDatabase extends Database {
public @NonNull List getSimilarRecipientIds(@NonNull Recipient recipient) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
- String[] projection = SqlUtil.buildArgs(ID, "COALESCE(" + nullIfEmpty(SYSTEM_DISPLAY_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ") AS checked_name");
+ String[] projection = SqlUtil.buildArgs(ID, "COALESCE(" + nullIfEmpty(SYSTEM_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ") AS checked_name");
String where = "checked_name = ?";
String[] arguments = SqlUtil.buildArgs(recipient.getProfileName().toString());
@@ -2246,7 +2250,7 @@ public class RecipientDatabase extends Database {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
List results = new LinkedList<>();
- try (Cursor cursor = db.query(TABLE_NAME, ID_PROJECTION, SYSTEM_DISPLAY_NAME + " IS NOT NULL AND " + SYSTEM_DISPLAY_NAME + " != \"\"", null, null, null, null)) {
+ try (Cursor cursor = db.query(TABLE_NAME, ID_PROJECTION, SYSTEM_JOINED_NAME + " IS NOT NULL AND " + SYSTEM_JOINED_NAME + " != \"\"", null, null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
results.add(RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(ID))));
}
@@ -2260,10 +2264,10 @@ public class RecipientDatabase extends Database {
Map updates = new HashMap<>();
db.beginTransaction();
- try (Cursor cursor = db.query(TABLE_NAME, new String[] {ID, COLOR, SYSTEM_DISPLAY_NAME}, SYSTEM_DISPLAY_NAME + " IS NOT NULL AND " + SYSTEM_DISPLAY_NAME + " != \"\"", null, null, null, null)) {
+ try (Cursor cursor = db.query(TABLE_NAME, new String[] {ID, COLOR, SYSTEM_JOINED_NAME}, SYSTEM_JOINED_NAME + " IS NOT NULL AND " + SYSTEM_JOINED_NAME + " != \"\"", null, null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
- MaterialColor newColor = updater.update(cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_DISPLAY_NAME)),
+ MaterialColor newColor = updater.update(cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_JOINED_NAME)),
cursor.getString(cursor.getColumnIndexOrThrow(COLOR)));
ContentValues contentValues = new ContentValues(1);
@@ -2284,7 +2288,7 @@ public class RecipientDatabase extends Database {
String selection = BLOCKED + " = ? AND " +
REGISTERED + " = ? AND " +
GROUP_ID + " IS NULL AND " +
- "(" + SYSTEM_DISPLAY_NAME + " NOT NULL OR " + PROFILE_SHARING + " = ?) AND " +
+ "(" + SYSTEM_JOINED_NAME + " NOT NULL OR " + PROFILE_SHARING + " = ?) AND " +
"(" + SORT_NAME + " NOT NULL OR " + USERNAME + " NOT NULL)";
String[] args;
@@ -2295,7 +2299,7 @@ public class RecipientDatabase extends Database {
args = new String[] { "0", String.valueOf(RegisteredState.REGISTERED.getId()), "1", Recipient.self().getId().serialize() };
}
- String orderBy = SORT_NAME + ", " + SYSTEM_DISPLAY_NAME + ", " + SEARCH_PROFILE_NAME + ", " + USERNAME + ", " + PHONE;
+ String orderBy = SORT_NAME + ", " + SYSTEM_JOINED_NAME + ", " + SEARCH_PROFILE_NAME + ", " + USERNAME + ", " + PHONE;
return databaseHelper.getReadableDatabase().query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy);
}
@@ -2306,7 +2310,7 @@ public class RecipientDatabase extends Database {
String selection = BLOCKED + " = ? AND " +
REGISTERED + " = ? AND " +
GROUP_ID + " IS NULL AND " +
- "(" + SYSTEM_DISPLAY_NAME + " NOT NULL OR " + PROFILE_SHARING + " = ?) AND " +
+ "(" + SYSTEM_JOINED_NAME + " NOT NULL OR " + PROFILE_SHARING + " = ?) AND " +
"(" +
PHONE + " GLOB ? OR " +
SORT_NAME + " GLOB ? OR " +
@@ -2321,7 +2325,7 @@ public class RecipientDatabase extends Database {
args = new String[] { "0", String.valueOf(RegisteredState.REGISTERED.getId()), "1", query, query, query, String.valueOf(Recipient.self().getId().toLong()) };
}
- String orderBy = SORT_NAME + ", " + SYSTEM_DISPLAY_NAME + ", " + SEARCH_PROFILE_NAME + ", " + PHONE;
+ String orderBy = SORT_NAME + ", " + SYSTEM_JOINED_NAME + ", " + SEARCH_PROFILE_NAME + ", " + PHONE;
return databaseHelper.getReadableDatabase().query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy);
}
@@ -2330,10 +2334,10 @@ public class RecipientDatabase extends Database {
String selection = BLOCKED + " = ? AND " +
REGISTERED + " != ? AND " +
GROUP_ID + " IS NULL AND " +
- SYSTEM_DISPLAY_NAME + " NOT NULL AND " +
+ SYSTEM_CONTACT_URI + " NOT NULL AND " +
"(" + PHONE + " NOT NULL OR " + EMAIL + " NOT NULL)";
String[] args = new String[] { "0", String.valueOf(RegisteredState.REGISTERED.getId()) };
- String orderBy = SYSTEM_DISPLAY_NAME + ", " + PHONE;
+ String orderBy = SYSTEM_JOINED_NAME + ", " + PHONE;
return databaseHelper.getReadableDatabase().query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy);
}
@@ -2344,15 +2348,15 @@ public class RecipientDatabase extends Database {
String selection = BLOCKED + " = ? AND " +
REGISTERED + " != ? AND " +
GROUP_ID + " IS NULL AND " +
- SYSTEM_DISPLAY_NAME + " NOT NULL AND " +
+ SYSTEM_CONTACT_URI + " NOT NULL AND " +
"(" + PHONE + " NOT NULL OR " + EMAIL + " NOT NULL) AND " +
"(" +
- PHONE + " GLOB ? OR " +
- EMAIL + " GLOB ? OR " +
- SYSTEM_DISPLAY_NAME + " GLOB ?" +
+ PHONE + " GLOB ? OR " +
+ EMAIL + " GLOB ? OR " +
+ SYSTEM_JOINED_NAME + " GLOB ?" +
")";
String[] args = new String[] { "0", String.valueOf(RegisteredState.REGISTERED.getId()), query, query, query };
- String orderBy = SYSTEM_DISPLAY_NAME + ", " + PHONE;
+ String orderBy = SYSTEM_JOINED_NAME + ", " + PHONE;
return databaseHelper.getReadableDatabase().query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy);
}
@@ -2429,7 +2433,7 @@ public class RecipientDatabase extends Database {
String selection = REGISTERED + " = ? AND " +
GROUP_ID + " IS NULL AND " +
ID + " != ? AND " +
- "(" + SYSTEM_DISPLAY_NAME + " NOT NULL OR " + ID + " IN (" + subquery + "))";
+ "(" + SYSTEM_CONTACT_URI + " NOT NULL OR " + ID + " IN (" + subquery + "))";
String[] args = new String[] { String.valueOf(RegisteredState.REGISTERED.getId()), Recipient.self().getId().serialize() };
List recipients = new ArrayList<>();
@@ -2729,7 +2733,9 @@ public class RecipientDatabase extends Database {
uuidValues.put(DEFAULT_SUBSCRIPTION_ID, e164Settings.getDefaultSubscriptionId().or(-1));
uuidValues.put(MESSAGE_EXPIRATION_TIME, uuidSettings.getExpireMessages() > 0 ? uuidSettings.getExpireMessages() : e164Settings.getExpireMessages());
uuidValues.put(REGISTERED, RegisteredState.REGISTERED.getId());
- uuidValues.put(SYSTEM_DISPLAY_NAME, e164Settings.getSystemDisplayName());
+ uuidValues.put(SYSTEM_GIVEN_NAME, e164Settings.getSystemProfileName().getGivenName());
+ uuidValues.put(SYSTEM_FAMILY_NAME, e164Settings.getSystemProfileName().getFamilyName());
+ uuidValues.put(SYSTEM_JOINED_NAME, e164Settings.getSystemProfileName().toString());
uuidValues.put(SYSTEM_PHOTO_URI, e164Settings.getSystemContactPhotoUri());
uuidValues.put(SYSTEM_PHONE_LABEL, e164Settings.getSystemPhoneLabel());
uuidValues.put(SYSTEM_CONTACT_URI, e164Settings.getSystemContactUri());
@@ -2840,14 +2846,16 @@ public class RecipientDatabase extends Database {
}
public void setSystemContactInfo(@NonNull RecipientId id,
- @Nullable String displayName,
+ @NonNull ProfileName systemProfileName,
@Nullable String photoUri,
@Nullable String systemPhoneLabel,
int systemPhoneType,
@Nullable String systemContactUri)
{
ContentValues dirtyQualifyingValues = new ContentValues();
- dirtyQualifyingValues.put(SYSTEM_DISPLAY_NAME, displayName);
+ dirtyQualifyingValues.put(SYSTEM_GIVEN_NAME, systemProfileName.getGivenName());
+ dirtyQualifyingValues.put(SYSTEM_FAMILY_NAME, systemProfileName.getFamilyName());
+ dirtyQualifyingValues.put(SYSTEM_JOINED_NAME, systemProfileName.toString());
if (update(id, dirtyQualifyingValues)) {
markDirty(id, DirtyState.UPDATE);
@@ -2859,11 +2867,12 @@ public class RecipientDatabase extends Database {
refreshQualifyingValues.put(SYSTEM_PHONE_TYPE, systemPhoneType);
refreshQualifyingValues.put(SYSTEM_CONTACT_URI, systemContactUri);
+ String joinedName = systemProfileName.toString();
boolean updatedValues = update(id, refreshQualifyingValues);
- boolean updatedColor = displayName != null && setColorIfNotSetInternal(id, ContactColors.generateFor(displayName));
+ boolean updatedColor = !TextUtils.isEmpty(joinedName) && setColorIfNotSetInternal(id, ContactColors.generateFor(joinedName));
if (updatedValues || updatedColor) {
- pendingContactInfoMap.put(id, new PendingContactInfo(displayName, photoUri, systemPhoneLabel, systemContactUri));
+ pendingContactInfoMap.put(id, new PendingContactInfo(systemProfileName, photoUri, systemPhoneLabel, systemContactUri));
}
ContentValues otherValues = new ContentValues();
@@ -2898,7 +2907,9 @@ public class RecipientDatabase extends Database {
ContentValues values = new ContentValues(5);
values.put(SYSTEM_INFO_PENDING, 0);
- values.put(SYSTEM_DISPLAY_NAME, (String) null);
+ values.put(SYSTEM_GIVEN_NAME, (String) null);
+ values.put(SYSTEM_FAMILY_NAME, (String) null);
+ values.put(SYSTEM_JOINED_NAME, (String) null);
values.put(SYSTEM_PHOTO_URI, (String) null);
values.put(SYSTEM_PHONE_LABEL, (String) null);
values.put(SYSTEM_CONTACT_URI, (String) null);
@@ -2939,7 +2950,7 @@ public class RecipientDatabase extends Database {
private final RegisteredState registered;
private final byte[] profileKey;
private final ProfileKeyCredential profileKeyCredential;
- private final String systemDisplayName;
+ private final ProfileName systemProfileName;
private final String systemContactPhoto;
private final String systemPhoneLabel;
private final String systemContactUri;
@@ -2981,7 +2992,7 @@ public class RecipientDatabase extends Database {
@NonNull RegisteredState registered,
@Nullable byte[] profileKey,
@Nullable ProfileKeyCredential profileKeyCredential,
- @Nullable String systemDisplayName,
+ @NonNull ProfileName systemProfileName,
@Nullable String systemContactPhoto,
@Nullable String systemPhoneLabel,
@Nullable String systemContactUri,
@@ -3021,7 +3032,7 @@ public class RecipientDatabase extends Database {
this.registered = registered;
this.profileKey = profileKey;
this.profileKeyCredential = profileKeyCredential;
- this.systemDisplayName = systemDisplayName;
+ this.systemProfileName = systemProfileName;
this.systemContactPhoto = systemContactPhoto;
this.systemPhoneLabel = systemPhoneLabel;
this.systemContactUri = systemContactUri;
@@ -3125,8 +3136,8 @@ public class RecipientDatabase extends Database {
return profileKeyCredential;
}
- public @Nullable String getSystemDisplayName() {
- return systemDisplayName;
+ public @NonNull ProfileName getSystemProfileName() {
+ return systemProfileName;
}
public @Nullable String getSystemContactPhotoUri() {
@@ -3313,13 +3324,13 @@ public class RecipientDatabase extends Database {
private static class PendingContactInfo {
- private final String displayName;
- private final String photoUri;
- private final String phoneLabel;
- private final String contactUri;
+ private final ProfileName profileName;
+ private final String photoUri;
+ private final String phoneLabel;
+ private final String contactUri;
- private PendingContactInfo(String displayName, String photoUri, String phoneLabel, String contactUri) {
- this.displayName = displayName;
+ private PendingContactInfo(@NonNull ProfileName systemProfileName, String photoUri, String phoneLabel, String contactUri) {
+ this.profileName = systemProfileName;
this.photoUri = photoUri;
this.phoneLabel = phoneLabel;
this.contactUri = contactUri;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java
index 6bc818945..95bcbdfe4 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java
@@ -171,8 +171,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
private static final int LAST_RESET_SESSION_TIME = 87;
private static final int WALLPAPER = 88;
private static final int ABOUT = 89;
+ private static final int SPLIT_SYSTEM_NAMES = 90;
- private static final int DATABASE_VERSION = 89;
+ private static final int DATABASE_VERSION = 90;
private static final String DATABASE_NAME = "signal.db";
private final Context context;
@@ -1258,6 +1259,12 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
db.execSQL("ALTER TABLE recipient ADD COLUMN about_emoji TEXT DEFAULT NULL");
}
+ if (oldVersion < SPLIT_SYSTEM_NAMES) {
+ db.execSQL("ALTER TABLE recipient ADD COLUMN system_family_name TEXT DEFAULT NULL");
+ db.execSQL("ALTER TABLE recipient ADD COLUMN system_given_name TEXT DEFAULT NULL");
+ db.execSQL("UPDATE recipient SET system_given_name = system_display_name");
+ }
+
db.setTransactionSuccessful();
} finally {
db.endTransaction();
diff --git a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsRepository.java b/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsRepository.java
index cab776f01..467226ee8 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsRepository.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsRepository.java
@@ -66,7 +66,7 @@ public class InsightsRepository implements InsightsDashboardViewModel.Repository
public void getUserAvatar(@NonNull Consumer avatarConsumer) {
SimpleTask.run(() -> {
Recipient self = Recipient.self().resolve();
- String name = Optional.fromNullable(self.getName(context)).or("");
+ String name = Optional.fromNullable(self.getDisplayName(context)).or("");
MaterialColor fallbackColor = self.getColor();
if (fallbackColor == ContactColors.UNKNOWN_COLOR && !TextUtils.isEmpty(name)) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java
index 9fe83a548..aa90945a0 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java
@@ -136,7 +136,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob {
Set archived = DatabaseFactory.getThreadDatabase(context).getArchivedRecipients();
out.write(new DeviceContact(RecipientUtil.toSignalServiceAddress(context, recipient),
- Optional.fromNullable(recipient.getName(context)),
+ Optional.fromNullable(recipient.isGroup() || recipient.isSystemContact() ? recipient.getDisplayName(context) : null),
getAvatar(recipient.getId(), recipient.getContactUri()),
Optional.fromNullable(recipient.getColor().serialize()),
verifiedMessage,
@@ -191,7 +191,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob {
for (Recipient recipient : recipients) {
Optional identity = DatabaseFactory.getIdentityDatabase(context).getIdentity(recipient.getId());
Optional verified = getVerifiedMessage(recipient, identity);
- Optional name = Optional.fromNullable(recipient.getName(context));
+ Optional name = Optional.fromNullable(recipient.isSystemContact() ? recipient.getDisplayName(context) : recipient.getGroupName(context));
Optional color = Optional.of(recipient.getColor().serialize());
Optional profileKey = ProfileKeyUtil.profileKeyOptional(recipient.getProfileKey());
boolean blocked = recipient.isBlocked();
diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java
index 1412c4a80..3b5b2eaa7 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java
@@ -70,6 +70,7 @@ public class ApplicationMigrations {
static final int USER_NOTIFICATION = 25;
static final int DAY_BY_DAY_STICKERS = 26;
static final int BLOB_LOCATION = 27;
+ static final int SYSTEM_NAME_SPLIT = 28;
}
/**
@@ -296,6 +297,10 @@ public class ApplicationMigrations {
jobs.put(Version.BLOB_LOCATION, new BlobStorageLocationMigrationJob());
}
+ if (lastSeenVersion < Version.SYSTEM_NAME_SPLIT) {
+ jobs.put(Version.SYSTEM_NAME_SPLIT, new DirectoryRefreshMigrationJob());
+ }
+
return jobs;
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/ProfileName.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/ProfileName.java
index daca12f45..dba8634f1 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/profiles/ProfileName.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/ProfileName.java
@@ -89,6 +89,13 @@ public final class ProfileName implements Parcelable {
}
}
+ /**
+ * Creates a profile name that only contains a given name.
+ */
+ public static @NonNull ProfileName asGiven(@Nullable String givenName) {
+ return fromParts(givenName, null);
+ }
+
/**
* Creates a profile name, trimming chars until it fits the limits.
*/
diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditGroupProfileRepository.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditGroupProfileRepository.java
index 39a81600c..3951d3997 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditGroupProfileRepository.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditGroupProfileRepository.java
@@ -74,7 +74,7 @@ class EditGroupProfileRepository implements EditProfileRepository {
String title = groupRecord.getTitle();
return title == null ? "" : title;
})
- .or(() -> recipient.getName(context));
+ .or(() -> recipient.getGroupName(context));
}, nameConsumer::accept);
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java
index e9da73848..c2159827b 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java
@@ -89,11 +89,11 @@ public class Recipient {
private final RegisteredState registered;
private final byte[] profileKey;
private final ProfileKeyCredential profileKeyCredential;
- private final String name;
+ private final String groupName;
private final Uri systemContactPhoto;
private final String customLabel;
private final Uri contactUri;
- private final ProfileName profileName;
+ private final ProfileName signalProfileName;
private final String profileAvatar;
private final boolean hasProfileImage;
private final boolean profileSharing;
@@ -109,6 +109,7 @@ public class Recipient {
private final ChatWallpaper wallpaper;
private final String about;
private final String aboutEmoji;
+ private final ProfileName systemProfileName;
/**
@@ -326,11 +327,11 @@ public class Recipient {
this.registered = RegisteredState.UNKNOWN;
this.profileKey = null;
this.profileKeyCredential = null;
- this.name = null;
+ this.groupName = null;
this.systemContactPhoto = null;
this.customLabel = null;
this.contactUri = null;
- this.profileName = ProfileName.EMPTY;
+ this.signalProfileName = ProfileName.EMPTY;
this.profileAvatar = null;
this.hasProfileImage = false;
this.profileSharing = false;
@@ -345,6 +346,7 @@ public class Recipient {
this.wallpaper = null;
this.about = null;
this.aboutEmoji = null;
+ this.systemProfileName = ProfileName.EMPTY;
}
public Recipient(@NonNull RecipientId id, @NonNull RecipientDetails details, boolean resolved) {
@@ -371,11 +373,11 @@ public class Recipient {
this.registered = details.registered;
this.profileKey = details.profileKey;
this.profileKeyCredential = details.profileKeyCredential;
- this.name = details.name;
+ this.groupName = details.groupName;
this.systemContactPhoto = details.systemContactPhoto;
this.customLabel = details.customLabel;
this.contactUri = details.contactUri;
- this.profileName = details.profileName;
+ this.signalProfileName = details.profileName;
this.profileAvatar = details.profileAvatar;
this.hasProfileImage = details.hasProfileImage;
this.profileSharing = details.profileSharing;
@@ -390,6 +392,7 @@ public class Recipient {
this.wallpaper = details.wallpaper;
this.about = details.about;
this.aboutEmoji = details.aboutEmoji;
+ this.systemProfileName = details.systemProfileName;
}
public @NonNull RecipientId getId() {
@@ -404,8 +407,8 @@ public class Recipient {
return contactUri;
}
- public @Nullable String getName(@NonNull Context context) {
- if (this.name == null && groupId != null && groupId.isMms()) {
+ public @Nullable String getGroupName(@NonNull Context context) {
+ if (this.groupName == null && groupId != null && groupId.isMms()) {
List names = new LinkedList<>();
for (Recipient recipient : participants) {
@@ -413,27 +416,32 @@ public class Recipient {
}
return Util.join(names, ", ");
- } else if (name == null && groupId != null && groupId.isPush()) {
+ } else if (groupName == null && groupId != null && groupId.isPush()) {
return context.getString(R.string.RecipientProvider_unnamed_group);
} else {
- return this.name;
+ return this.groupName;
}
}
public boolean hasName() {
- return name != null;
+ return groupName != null;
}
/**
* False iff it {@link #getDisplayName} would fall back to e164, email or unknown.
*/
public boolean hasAUserSetDisplayName(@NonNull Context context) {
- return !TextUtils.isEmpty(getName(context)) ||
+ return !TextUtils.isEmpty(getGroupName(context)) ||
+ !TextUtils.isEmpty(getSystemProfileName().toString()) ||
!TextUtils.isEmpty(getProfileName().toString());
}
public @NonNull String getDisplayName(@NonNull Context context) {
- String name = getName(context);
+ String name = getGroupName(context);
+
+ if (Util.isEmpty(name)) {
+ name = getSystemProfileName().toString();
+ }
if (Util.isEmpty(name)) {
name = getProfileName().toString();
@@ -455,7 +463,11 @@ public class Recipient {
}
public @NonNull String getDisplayNameOrUsername(@NonNull Context context) {
- String name = getName(context);
+ String name = getGroupName(context);
+
+ if (Util.isEmpty(name)) {
+ name = getSystemProfileName().toString();
+ }
if (Util.isEmpty(name)) {
name = StringUtil.isolateBidi(getProfileName().toString());
@@ -481,11 +493,16 @@ public class Recipient {
}
public @NonNull String getMentionDisplayName(@NonNull Context context) {
- String name = isSelf ? getProfileName().toString() : getName(context);
+ String name = isSelf ? getProfileName().toString() : getGroupName(context);
name = StringUtil.isolateBidi(name);
if (Util.isEmpty(name)) {
- name = isSelf ? getName(context) : getProfileName().toString();
+ name = isSelf ? getGroupName(context) : getSystemProfileName().toString();
+ name = StringUtil.isolateBidi(name);
+ }
+
+ if (Util.isEmpty(name)) {
+ name = isSelf ? getGroupName(context) : getProfileName().toString();
name = StringUtil.isolateBidi(name);
}
@@ -505,7 +522,8 @@ public class Recipient {
}
public @NonNull String getShortDisplayName(@NonNull Context context) {
- String name = Util.getFirstNonEmpty(getName(context),
+ String name = Util.getFirstNonEmpty(getGroupName(context),
+ getSystemProfileName().getGivenName(),
getProfileName().getGivenName(),
getDisplayName(context));
@@ -513,7 +531,8 @@ public class Recipient {
}
public @NonNull String getShortDisplayNameIncludingUsername(@NonNull Context context) {
- String name = Util.getFirstNonEmpty(getName(context),
+ String name = Util.getFirstNonEmpty(getGroupName(context),
+ getSystemProfileName().getGivenName(),
getProfileName().getGivenName(),
getDisplayName(context),
getUsername().orNull());
@@ -526,7 +545,7 @@ public class Recipient {
return MaterialColor.GROUP;
} else if (color != null) {
return color;
- } else if (name != null || profileSharing) {
+ } else if (groupName != null || profileSharing) {
Log.w(TAG, "Had no color for " + id + "! Saving a new one.");
Context context = ApplicationDependencies.getApplication();
@@ -672,7 +691,11 @@ public class Recipient {
}
public @NonNull ProfileName getProfileName() {
- return profileName;
+ return signalProfileName;
+ }
+
+ private @NonNull ProfileName getSystemProfileName() {
+ return systemProfileName;
}
public @Nullable String getProfileAvatar() {
@@ -744,12 +767,12 @@ public class Recipient {
}
public @NonNull FallbackContactPhoto getFallbackContactPhoto(@NonNull FallbackPhotoProvider fallbackPhotoProvider) {
- if (isSelf) return fallbackPhotoProvider.getPhotoForLocalNumber();
- else if (isResolving()) return fallbackPhotoProvider.getPhotoForResolvingRecipient();
- else if (isGroupInternal()) return fallbackPhotoProvider.getPhotoForGroup();
- else if (isGroup()) return fallbackPhotoProvider.getPhotoForGroup();
- else if (!TextUtils.isEmpty(name)) return fallbackPhotoProvider.getPhotoForRecipientWithName(name);
- else return fallbackPhotoProvider.getPhotoForRecipientWithoutName();
+ if (isSelf) return fallbackPhotoProvider.getPhotoForLocalNumber();
+ else if (isResolving()) return fallbackPhotoProvider.getPhotoForResolvingRecipient();
+ else if (isGroupInternal()) return fallbackPhotoProvider.getPhotoForGroup();
+ else if (isGroup()) return fallbackPhotoProvider.getPhotoForGroup();
+ else if (!TextUtils.isEmpty(groupName)) return fallbackPhotoProvider.getPhotoForRecipientWithName(groupName);
+ else return fallbackPhotoProvider.getPhotoForRecipientWithoutName();
}
public @Nullable ContactPhoto getContactPhoto() {
@@ -1003,11 +1026,12 @@ public class Recipient {
registered == other.registered &&
Arrays.equals(profileKey, other.profileKey) &&
Objects.equals(profileKeyCredential, other.profileKeyCredential) &&
- Objects.equals(name, other.name) &&
+ Objects.equals(groupName, other.groupName) &&
Objects.equals(systemContactPhoto, other.systemContactPhoto) &&
Objects.equals(customLabel, other.customLabel) &&
Objects.equals(contactUri, other.contactUri) &&
- Objects.equals(profileName, other.profileName) &&
+ Objects.equals(signalProfileName, other.signalProfileName) &&
+ Objects.equals(systemProfileName, other.systemProfileName) &&
Objects.equals(profileAvatar, other.profileAvatar) &&
Objects.equals(notificationChannel, other.notificationChannel) &&
unidentifiedAccessMode == other.unidentifiedAccessMode &&
diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java
index d8af3b51a..284fcdd4e 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java
@@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.recipients;
import android.content.Context;
import android.net.Uri;
-import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -33,7 +32,7 @@ public class RecipientDetails {
final String e164;
final String email;
final GroupId groupId;
- final String name;
+ final String groupName;
final String customLabel;
final Uri systemContactPhoto;
final Uri contactUri;
@@ -69,8 +68,9 @@ public class RecipientDetails {
final ChatWallpaper wallpaper;
final String about;
final String aboutEmoji;
+ final ProfileName systemProfileName;
- public RecipientDetails(@Nullable String name,
+ public RecipientDetails(@Nullable String groupName,
@NonNull Optional groupAvatarId,
boolean systemContact,
boolean isSelf,
@@ -117,9 +117,8 @@ public class RecipientDetails {
this.wallpaper = settings.getWallpaper();
this.about = settings.getAbout();
this.aboutEmoji = settings.getAboutEmoji();
-
- if (name == null) this.name = settings.getSystemDisplayName();
- else this.name = name;
+ this.systemProfileName = settings.getSystemProfileName();
+ this.groupName = groupName;
}
/**
@@ -159,7 +158,7 @@ public class RecipientDetails {
this.notificationChannel = null;
this.unidentifiedAccessMode = UnidentifiedAccessMode.UNKNOWN;
this.forceSmsSelection = false;
- this.name = null;
+ this.groupName = null;
this.groupsV2Capability = Recipient.Capability.UNKNOWN;
this.groupsV1MigrationCapability = Recipient.Capability.UNKNOWN;
this.storageId = null;
@@ -167,10 +166,11 @@ public class RecipientDetails {
this.wallpaper = null;
this.about = null;
this.aboutEmoji = null;
+ this.systemProfileName = ProfileName.EMPTY;
}
public static @NonNull RecipientDetails forIndividual(@NonNull Context context, @NonNull RecipientSettings settings) {
- boolean systemContact = !TextUtils.isEmpty(settings.getSystemDisplayName());
+ boolean systemContact = !settings.getSystemProfileName().isEmpty();
boolean isSelf = (settings.getE164() != null && settings.getE164().equals(TextSecurePreferences.getLocalNumber(context))) ||
(settings.getUuid() != null && settings.getUuid().equals(TextSecurePreferences.getLocalUuid(context)));