kopia lustrzana https://github.com/ryukoposting/Signal-Android
1379 wiersze
49 KiB
Java
1379 wiersze
49 KiB
Java
package org.thoughtcrime.securesms.recipients;
|
|
|
|
import android.content.Context;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.net.Uri;
|
|
import android.text.TextUtils;
|
|
|
|
import androidx.annotation.AnyThread;
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.annotation.WorkerThread;
|
|
|
|
import com.annimon.stream.Stream;
|
|
|
|
import org.signal.core.util.StringUtil;
|
|
import org.signal.core.util.logging.Log;
|
|
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
|
|
import org.thoughtcrime.securesms.R;
|
|
import org.thoughtcrime.securesms.badges.models.Badge;
|
|
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
|
|
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
|
|
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto;
|
|
import org.thoughtcrime.securesms.contacts.avatars.GroupRecordContactPhoto;
|
|
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
|
|
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
|
|
import org.thoughtcrime.securesms.contacts.avatars.SystemContactPhoto;
|
|
import org.thoughtcrime.securesms.contacts.avatars.TransparentContactPhoto;
|
|
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
|
|
import org.thoughtcrime.securesms.conversation.colors.ChatColors;
|
|
import org.thoughtcrime.securesms.conversation.colors.ChatColorsPalette;
|
|
import org.thoughtcrime.securesms.database.RecipientTable;
|
|
import org.thoughtcrime.securesms.database.RecipientTable.MentionSetting;
|
|
import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState;
|
|
import org.thoughtcrime.securesms.database.RecipientTable.UnidentifiedAccessMode;
|
|
import org.thoughtcrime.securesms.database.RecipientTable.VibrateState;
|
|
import org.thoughtcrime.securesms.database.SignalDatabase;
|
|
import org.thoughtcrime.securesms.database.model.DistributionListId;
|
|
import org.thoughtcrime.securesms.database.model.ProfileAvatarFileDetails;
|
|
import org.thoughtcrime.securesms.database.model.RecipientRecord;
|
|
import org.thoughtcrime.securesms.database.model.databaseprotos.RecipientExtras;
|
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
|
import org.thoughtcrime.securesms.groups.GroupId;
|
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
|
import org.thoughtcrime.securesms.phonenumbers.NumberUtil;
|
|
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
|
|
import org.thoughtcrime.securesms.profiles.ProfileName;
|
|
import org.thoughtcrime.securesms.util.AvatarUtil;
|
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
|
import org.thoughtcrime.securesms.util.Util;
|
|
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
|
|
import org.whispersystems.signalservice.api.push.PNI;
|
|
import org.whispersystems.signalservice.api.push.ServiceId;
|
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
|
import org.whispersystems.signalservice.api.util.OptionalUtil;
|
|
import org.whispersystems.signalservice.api.util.Preconditions;
|
|
import org.whispersystems.signalservice.api.util.UuidUtil;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.Optional;
|
|
import java.util.stream.Collectors;
|
|
|
|
import io.reactivex.rxjava3.core.Observable;
|
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
|
|
|
import static org.thoughtcrime.securesms.database.RecipientTable.InsightsBannerTier;
|
|
|
|
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
|
public class Recipient {
|
|
|
|
private static final String TAG = Log.tag(Recipient.class);
|
|
|
|
public static final Recipient UNKNOWN = new Recipient(RecipientId.UNKNOWN, RecipientDetails.forUnknown(), true);
|
|
|
|
public static final FallbackPhotoProvider DEFAULT_FALLBACK_PHOTO_PROVIDER = new FallbackPhotoProvider();
|
|
|
|
private static final int MAX_MEMBER_NAMES = 10;
|
|
|
|
private final RecipientId id;
|
|
private final boolean resolving;
|
|
private final ServiceId serviceId;
|
|
private final PNI pni;
|
|
private final String username;
|
|
private final String e164;
|
|
private final String email;
|
|
private final GroupId groupId;
|
|
private final DistributionListId distributionListId;
|
|
private final List<RecipientId> participantIds;
|
|
private final Optional<Long> groupAvatarId;
|
|
private final boolean isSelf;
|
|
private final boolean blocked;
|
|
private final long muteUntil;
|
|
private final VibrateState messageVibrate;
|
|
private final VibrateState callVibrate;
|
|
private final Uri messageRingtone;
|
|
private final Uri callRingtone;
|
|
private final Optional<Integer> defaultSubscriptionId;
|
|
private final int expireMessages;
|
|
private final RegisteredState registered;
|
|
private final byte[] profileKey;
|
|
private final ExpiringProfileKeyCredential expiringProfileKeyCredential;
|
|
private final String groupName;
|
|
private final Uri systemContactPhoto;
|
|
private final String customLabel;
|
|
private final Uri contactUri;
|
|
private final ProfileName signalProfileName;
|
|
private final String profileAvatar;
|
|
private final ProfileAvatarFileDetails profileAvatarFileDetails;
|
|
private final boolean profileSharing;
|
|
private final long lastProfileFetch;
|
|
private final String notificationChannel;
|
|
private final UnidentifiedAccessMode unidentifiedAccessMode;
|
|
private final boolean forceSmsSelection;
|
|
private final RecipientRecord.Capabilities capabilities;
|
|
private final InsightsBannerTier insightsBannerTier;
|
|
private final byte[] storageId;
|
|
private final MentionSetting mentionSetting;
|
|
private final ChatWallpaper wallpaper;
|
|
private final ChatColors chatColors;
|
|
private final AvatarColor avatarColor;
|
|
private final String about;
|
|
private final String aboutEmoji;
|
|
private final ProfileName systemProfileName;
|
|
private final String systemContactName;
|
|
private final Optional<Extras> extras;
|
|
private final boolean hasGroupsInCommon;
|
|
private final List<Badge> badges;
|
|
private final boolean isReleaseNotesRecipient;
|
|
private final boolean needsPniSignature;
|
|
|
|
/**
|
|
* Returns a {@link LiveRecipient}, which contains a {@link Recipient} that may or may not be
|
|
* populated with data. However, you can observe the value that's returned to be notified when the
|
|
* {@link Recipient} changes.
|
|
*/
|
|
@AnyThread
|
|
public static @NonNull LiveRecipient live(@NonNull RecipientId id) {
|
|
Preconditions.checkNotNull(id, "ID cannot be null.");
|
|
return ApplicationDependencies.getRecipientCache().getLive(id);
|
|
}
|
|
|
|
/**
|
|
* Returns a live recipient wrapped in an Observable. All work is done on the IO threadpool.
|
|
*/
|
|
@AnyThread
|
|
public static @NonNull Observable<Recipient> observable(@NonNull RecipientId id) {
|
|
Preconditions.checkNotNull(id, "ID cannot be null");
|
|
return Observable.<Recipient>create(emitter -> {
|
|
LiveRecipient live = live(id);
|
|
emitter.onNext(live.resolve());
|
|
|
|
RecipientForeverObserver observer = emitter::onNext;
|
|
|
|
live.observeForever(observer);
|
|
emitter.setCancellable(() -> {
|
|
live.removeForeverObserver(observer);
|
|
});
|
|
}).subscribeOn(Schedulers.io());
|
|
}
|
|
|
|
/**
|
|
* Returns a fully-populated {@link Recipient}. May hit the disk, and therefore should be
|
|
* called on a background thread.
|
|
*/
|
|
@WorkerThread
|
|
public static @NonNull Recipient resolved(@NonNull RecipientId id) {
|
|
Preconditions.checkNotNull(id, "ID cannot be null.");
|
|
return live(id).resolve();
|
|
}
|
|
|
|
@WorkerThread
|
|
public static @NonNull List<Recipient> resolvedList(@NonNull Collection<RecipientId> ids) {
|
|
List<Recipient> recipients = new ArrayList<>(ids.size());
|
|
|
|
for (RecipientId recipientId : ids) {
|
|
recipients.add(resolved(recipientId));
|
|
}
|
|
|
|
return recipients;
|
|
}
|
|
|
|
@WorkerThread
|
|
public static @NonNull Recipient distributionList(@NonNull DistributionListId distributionListId) {
|
|
RecipientId id = SignalDatabase.recipients().getOrInsertFromDistributionListId(distributionListId);
|
|
return resolved(id);
|
|
}
|
|
|
|
/**
|
|
* Returns a fully-populated {@link Recipient} and associates it with the provided username.
|
|
*/
|
|
@WorkerThread
|
|
public static @NonNull Recipient externalUsername(@NonNull ServiceId serviceId, @NonNull String username) {
|
|
Recipient recipient = externalPush(serviceId);
|
|
SignalDatabase.recipients().setUsername(recipient.getId(), username);
|
|
return recipient;
|
|
}
|
|
|
|
/**
|
|
* Returns a fully-populated {@link Recipient} based off of a {@link SignalServiceAddress},
|
|
* creating one in the database if necessary.
|
|
*/
|
|
@WorkerThread
|
|
public static @NonNull Recipient externalPush(@NonNull SignalServiceAddress signalServiceAddress) {
|
|
return externalPush(signalServiceAddress.getServiceId(), signalServiceAddress.getNumber().orElse(null));
|
|
}
|
|
|
|
/**
|
|
* Returns a fully-populated {@link Recipient} based off of a ServiceId, creating one
|
|
* in the database if necessary.
|
|
*/
|
|
@WorkerThread
|
|
public static @NonNull Recipient externalPush(@NonNull ServiceId serviceId) {
|
|
return externalPush(serviceId, null);
|
|
}
|
|
|
|
/**
|
|
* Create a recipient with a full (ACI, PNI, E164) tuple. It is assumed that the association between the PNI and serviceId is trusted.
|
|
* That means it must be from either storage service or a PNI verification message.
|
|
*/
|
|
public static @NonNull Recipient trustedPush(@NonNull ServiceId serviceId, @Nullable PNI pni, @Nullable String e164) {
|
|
if (ServiceId.UNKNOWN.equals(serviceId)) {
|
|
throw new AssertionError("Unknown serviceId!");
|
|
}
|
|
|
|
RecipientTable db = SignalDatabase.recipients();
|
|
|
|
RecipientId recipientId;
|
|
|
|
if (FeatureFlags.phoneNumberPrivacy()) {
|
|
recipientId = db.getAndPossiblyMergePnpVerified(serviceId, pni, e164);
|
|
} else {
|
|
recipientId = db.getAndPossiblyMerge(serviceId, e164);
|
|
}
|
|
|
|
Recipient resolved = resolved(recipientId);
|
|
|
|
if (!resolved.getId().equals(recipientId)) {
|
|
Log.w(TAG, "Resolved " + recipientId + ", but got back a recipient with " + resolved.getId());
|
|
}
|
|
|
|
if (!resolved.isRegistered()) {
|
|
Log.w(TAG, "External push was locally marked unregistered. Marking as registered.");
|
|
db.markRegistered(recipientId, serviceId);
|
|
}
|
|
|
|
return resolved;
|
|
}
|
|
|
|
/**
|
|
* Returns a fully-populated {@link Recipient} based off of a ServiceId and phone number, creating one
|
|
* in the database if necessary. We want both piece of information so we're able to associate them
|
|
* both together, depending on which are available.
|
|
*
|
|
* In particular, while we'll eventually get the ACI of a user created via a phone number
|
|
* (through a directory sync), the only way we can store the phone number is by retrieving it from
|
|
* sent messages and whatnot. So we should store it when available.
|
|
*/
|
|
@WorkerThread
|
|
static @NonNull Recipient externalPush(@Nullable ServiceId serviceId, @Nullable String e164) {
|
|
if (ServiceId.UNKNOWN.equals(serviceId)) {
|
|
throw new AssertionError();
|
|
}
|
|
|
|
RecipientTable db = SignalDatabase.recipients();
|
|
RecipientId recipientId = db.getAndPossiblyMerge(serviceId, e164);
|
|
|
|
Recipient resolved = resolved(recipientId);
|
|
|
|
if (!resolved.getId().equals(recipientId)) {
|
|
Log.w(TAG, "Resolved " + recipientId + ", but got back a recipient with " + resolved.getId());
|
|
}
|
|
|
|
if (!resolved.isRegistered() && serviceId != null) {
|
|
Log.w(TAG, "External push was locally marked unregistered. Marking as registered.");
|
|
db.markRegistered(recipientId, serviceId);
|
|
} else if (!resolved.isRegistered()) {
|
|
Log.w(TAG, "External push was locally marked unregistered, but we don't have an ACI, so we can't do anything.", new Throwable());
|
|
}
|
|
|
|
return resolved;
|
|
}
|
|
|
|
/**
|
|
* A safety wrapper around {@link #external(Context, String)} for when you know you're using an
|
|
* identifier for a system contact, and therefore always want to prevent interpreting it as a
|
|
* UUID. This will crash if given a UUID.
|
|
*
|
|
* (This may seem strange, but apparently some devices are returning valid UUIDs for contacts)
|
|
*/
|
|
@WorkerThread
|
|
public static @NonNull Recipient externalContact(@NonNull String identifier) {
|
|
RecipientTable db = SignalDatabase.recipients();
|
|
RecipientId id = null;
|
|
|
|
if (UuidUtil.isUuid(identifier)) {
|
|
throw new AssertionError("UUIDs are not valid system contact identifiers!");
|
|
} else if (NumberUtil.isValidEmail(identifier)) {
|
|
id = db.getOrInsertFromEmail(identifier);
|
|
} else {
|
|
id = db.getOrInsertFromE164(identifier);
|
|
}
|
|
|
|
return Recipient.resolved(id);
|
|
}
|
|
|
|
/**
|
|
* A version of {@link #external(Context, String)} that should be used when you know the
|
|
* identifier is a groupId.
|
|
*
|
|
* Important: This will throw an exception if the groupId you're using could have been migrated.
|
|
* If you're dealing with inbound data, you should be using
|
|
* {@link #externalPossiblyMigratedGroup(GroupId)}, or checking the database before
|
|
* calling this method.
|
|
*/
|
|
@WorkerThread
|
|
public static @NonNull Recipient externalGroupExact(@NonNull GroupId groupId) {
|
|
return Recipient.resolved(SignalDatabase.recipients().getOrInsertFromGroupId(groupId));
|
|
}
|
|
|
|
/**
|
|
* Will give you one of:
|
|
* - The recipient that matches the groupId specified exactly
|
|
* - The recipient whose V1 ID would map to the provided V2 ID
|
|
* - The recipient whose V2 ID would be derived from the provided V1 ID
|
|
* - A newly-created recipient for the provided ID if none of the above match
|
|
*
|
|
* Important: You could get back a recipient with a different groupId than the one you provided.
|
|
* You should be very cautious when using the groupId on the returned recipient.
|
|
*/
|
|
@WorkerThread
|
|
public static @NonNull Recipient externalPossiblyMigratedGroup(@NonNull GroupId groupId) {
|
|
return Recipient.resolved(SignalDatabase.recipients().getOrInsertFromPossiblyMigratedGroupId(groupId));
|
|
}
|
|
|
|
/**
|
|
* Returns a fully-populated {@link Recipient} based off of a string identifier, creating one in
|
|
* the database if necessary. The identifier may be a uuid, phone number, email,
|
|
* or serialized groupId.
|
|
*
|
|
* If the identifier is a UUID of a Signal user, prefer using
|
|
* {@link #externalPush(ServiceId, String)} or its overload, as this will let us associate
|
|
* the phone number with the recipient.
|
|
*/
|
|
@WorkerThread
|
|
public static @NonNull Recipient external(@NonNull Context context, @NonNull String identifier) {
|
|
Preconditions.checkNotNull(identifier, "Identifier cannot be null!");
|
|
|
|
RecipientTable db = SignalDatabase.recipients();
|
|
RecipientId id = null;
|
|
|
|
if (UuidUtil.isUuid(identifier)) {
|
|
ServiceId serviceId = ServiceId.parseOrThrow(identifier);
|
|
id = db.getOrInsertFromServiceId(serviceId);
|
|
} else if (GroupId.isEncodedGroup(identifier)) {
|
|
id = db.getOrInsertFromGroupId(GroupId.parseOrThrow(identifier));
|
|
} else if (NumberUtil.isValidEmail(identifier)) {
|
|
id = db.getOrInsertFromEmail(identifier);
|
|
} else {
|
|
String e164 = PhoneNumberFormatter.get(context).format(identifier);
|
|
id = db.getOrInsertFromE164(e164);
|
|
}
|
|
|
|
return Recipient.resolved(id);
|
|
}
|
|
|
|
public static @NonNull Recipient self() {
|
|
return ApplicationDependencies.getRecipientCache().getSelf();
|
|
}
|
|
|
|
public static boolean isSelfSet() {
|
|
return ApplicationDependencies.getRecipientCache().getSelfId() != null;
|
|
}
|
|
|
|
Recipient(@NonNull RecipientId id) {
|
|
this.id = id;
|
|
this.resolving = true;
|
|
this.serviceId = null;
|
|
this.pni = null;
|
|
this.username = null;
|
|
this.e164 = null;
|
|
this.email = null;
|
|
this.groupId = null;
|
|
this.distributionListId = null;
|
|
this.participantIds = Collections.emptyList();
|
|
this.groupAvatarId = Optional.empty();
|
|
this.isSelf = false;
|
|
this.blocked = false;
|
|
this.muteUntil = 0;
|
|
this.messageVibrate = VibrateState.DEFAULT;
|
|
this.callVibrate = VibrateState.DEFAULT;
|
|
this.messageRingtone = null;
|
|
this.callRingtone = null;
|
|
this.insightsBannerTier = InsightsBannerTier.TIER_TWO;
|
|
this.defaultSubscriptionId = Optional.empty();
|
|
this.expireMessages = 0;
|
|
this.registered = RegisteredState.UNKNOWN;
|
|
this.profileKey = null;
|
|
this.expiringProfileKeyCredential = null;
|
|
this.groupName = null;
|
|
this.systemContactPhoto = null;
|
|
this.customLabel = null;
|
|
this.contactUri = null;
|
|
this.signalProfileName = ProfileName.EMPTY;
|
|
this.profileAvatar = null;
|
|
this.profileAvatarFileDetails = ProfileAvatarFileDetails.NO_DETAILS;
|
|
this.profileSharing = false;
|
|
this.lastProfileFetch = 0;
|
|
this.notificationChannel = null;
|
|
this.unidentifiedAccessMode = UnidentifiedAccessMode.DISABLED;
|
|
this.forceSmsSelection = false;
|
|
this.capabilities = RecipientRecord.Capabilities.UNKNOWN;
|
|
this.storageId = null;
|
|
this.mentionSetting = MentionSetting.ALWAYS_NOTIFY;
|
|
this.wallpaper = null;
|
|
this.chatColors = null;
|
|
this.avatarColor = AvatarColor.UNKNOWN;
|
|
this.about = null;
|
|
this.aboutEmoji = null;
|
|
this.systemProfileName = ProfileName.EMPTY;
|
|
this.systemContactName = null;
|
|
this.extras = Optional.empty();
|
|
this.hasGroupsInCommon = false;
|
|
this.badges = Collections.emptyList();
|
|
this.isReleaseNotesRecipient = false;
|
|
this.needsPniSignature = false;
|
|
}
|
|
|
|
public Recipient(@NonNull RecipientId id, @NonNull RecipientDetails details, boolean resolved) {
|
|
this.id = id;
|
|
this.resolving = !resolved;
|
|
this.serviceId = details.serviceId;
|
|
this.pni = details.pni;
|
|
this.username = details.username;
|
|
this.e164 = details.e164;
|
|
this.email = details.email;
|
|
this.groupId = details.groupId;
|
|
this.distributionListId = details.distributionListId;
|
|
this.participantIds = details.participantIds;
|
|
this.groupAvatarId = details.groupAvatarId;
|
|
this.isSelf = details.isSelf;
|
|
this.blocked = details.blocked;
|
|
this.muteUntil = details.mutedUntil;
|
|
this.messageVibrate = details.messageVibrateState;
|
|
this.callVibrate = details.callVibrateState;
|
|
this.messageRingtone = details.messageRingtone;
|
|
this.callRingtone = details.callRingtone;
|
|
this.insightsBannerTier = details.insightsBannerTier;
|
|
this.defaultSubscriptionId = details.defaultSubscriptionId;
|
|
this.expireMessages = details.expireMessages;
|
|
this.registered = details.registered;
|
|
this.profileKey = details.profileKey;
|
|
this.expiringProfileKeyCredential = details.expiringProfileKeyCredential;
|
|
this.groupName = details.groupName;
|
|
this.systemContactPhoto = details.systemContactPhoto;
|
|
this.customLabel = details.customLabel;
|
|
this.contactUri = details.contactUri;
|
|
this.signalProfileName = details.profileName;
|
|
this.profileAvatar = details.profileAvatar;
|
|
this.profileAvatarFileDetails = details.profileAvatarFileDetails;
|
|
this.profileSharing = details.profileSharing;
|
|
this.lastProfileFetch = details.lastProfileFetch;
|
|
this.notificationChannel = details.notificationChannel;
|
|
this.unidentifiedAccessMode = details.unidentifiedAccessMode;
|
|
this.forceSmsSelection = details.forceSmsSelection;
|
|
this.capabilities = details.capabilities;
|
|
this.storageId = details.storageId;
|
|
this.mentionSetting = details.mentionSetting;
|
|
this.wallpaper = details.wallpaper;
|
|
this.chatColors = details.chatColors;
|
|
this.avatarColor = details.avatarColor;
|
|
this.about = details.about;
|
|
this.aboutEmoji = details.aboutEmoji;
|
|
this.systemProfileName = details.systemProfileName;
|
|
this.systemContactName = details.systemContactName;
|
|
this.extras = details.extras;
|
|
this.hasGroupsInCommon = details.hasGroupsInCommon;
|
|
this.badges = details.badges;
|
|
this.isReleaseNotesRecipient = details.isReleaseChannel;
|
|
this.needsPniSignature = details.needsPniSignature;
|
|
}
|
|
|
|
public @NonNull RecipientId getId() {
|
|
return id;
|
|
}
|
|
|
|
public boolean isSelf() {
|
|
return isSelf;
|
|
}
|
|
|
|
public @Nullable Uri getContactUri() {
|
|
return contactUri;
|
|
}
|
|
|
|
public @Nullable String getGroupName(@NonNull Context context) {
|
|
if (groupId != null && Util.isEmpty(this.groupName)) {
|
|
RecipientId selfId = ApplicationDependencies.getRecipientCache().getSelfId();
|
|
List<Recipient> others = participantIds.stream()
|
|
.filter(id -> !id.equals(selfId))
|
|
.limit(MAX_MEMBER_NAMES)
|
|
.map(Recipient::resolved)
|
|
.collect(Collectors.toList());
|
|
|
|
Map<String, Integer> shortNameCounts = new HashMap<>();
|
|
|
|
for (Recipient participant : others) {
|
|
String shortName = participant.getShortDisplayName(context);
|
|
int count = Objects.requireNonNull(shortNameCounts.getOrDefault(shortName, 0));
|
|
|
|
shortNameCounts.put(shortName, count + 1);
|
|
}
|
|
|
|
List<String> names = new LinkedList<>();
|
|
|
|
for (Recipient participant : others) {
|
|
String shortName = participant.getShortDisplayName(context);
|
|
int count = Objects.requireNonNull(shortNameCounts.getOrDefault(shortName, 0));
|
|
|
|
if (count <= 1) {
|
|
names.add(shortName);
|
|
} else {
|
|
names.add(participant.getDisplayName(context));
|
|
}
|
|
}
|
|
|
|
if (participantIds.stream().anyMatch(id -> id.equals(selfId))) {
|
|
names.add(context.getString(R.string.Recipient_you));
|
|
}
|
|
|
|
return Util.join(names, ", ");
|
|
} else if (!resolving && isMyStory()) {
|
|
return context.getString(R.string.Recipient_my_story);
|
|
} else {
|
|
return this.groupName;
|
|
}
|
|
}
|
|
|
|
public boolean hasName() {
|
|
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(getGroupName(context)) ||
|
|
!TextUtils.isEmpty(systemContactName) ||
|
|
!TextUtils.isEmpty(getProfileName().toString());
|
|
}
|
|
|
|
public @NonNull String getDisplayName(@NonNull Context context) {
|
|
String name = getNameFromLocalData(context);
|
|
|
|
if (Util.isEmpty(name)) {
|
|
name = context.getString(R.string.Recipient_unknown);
|
|
}
|
|
|
|
return StringUtil.isolateBidi(name);
|
|
}
|
|
|
|
public @NonNull String getDisplayNameOrUsername(@NonNull Context context) {
|
|
String name = getNameFromLocalData(context);
|
|
|
|
if (Util.isEmpty(name)) {
|
|
name = StringUtil.isolateBidi(username);
|
|
}
|
|
|
|
if (Util.isEmpty(name)) {
|
|
name = StringUtil.isolateBidi(context.getString(R.string.Recipient_unknown));
|
|
}
|
|
|
|
return StringUtil.isolateBidi(name);
|
|
}
|
|
|
|
public boolean hasNonUsernameDisplayName(@NonNull Context context) {
|
|
return getNameFromLocalData(context) != null;
|
|
}
|
|
|
|
/**
|
|
* @return local name for user ignoring the username.
|
|
*/
|
|
private @Nullable String getNameFromLocalData(@NonNull Context context) {
|
|
String name = getGroupName(context);
|
|
|
|
if (Util.isEmpty(name)) {
|
|
name = systemContactName;
|
|
}
|
|
|
|
if (Util.isEmpty(name)) {
|
|
name = getProfileName().toString();
|
|
}
|
|
|
|
if (Util.isEmpty(name) && !Util.isEmpty(e164)) {
|
|
name = PhoneNumberFormatter.prettyPrint(e164);
|
|
}
|
|
|
|
if (Util.isEmpty(name)) {
|
|
name = email;
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
public @NonNull String getMentionDisplayName(@NonNull Context context) {
|
|
String name = isSelf ? getProfileName().toString() : getGroupName(context);
|
|
name = StringUtil.isolateBidi(name);
|
|
|
|
if (Util.isEmpty(name)) {
|
|
name = isSelf ? getGroupName(context) : systemContactName;
|
|
name = StringUtil.isolateBidi(name);
|
|
}
|
|
|
|
if (Util.isEmpty(name)) {
|
|
name = isSelf ? getGroupName(context) : getProfileName().toString();
|
|
name = StringUtil.isolateBidi(name);
|
|
}
|
|
|
|
if (Util.isEmpty(name) && !Util.isEmpty(e164)) {
|
|
name = PhoneNumberFormatter.prettyPrint(e164);
|
|
}
|
|
|
|
if (Util.isEmpty(name)) {
|
|
name = StringUtil.isolateBidi(email);
|
|
}
|
|
|
|
if (Util.isEmpty(name)) {
|
|
name = StringUtil.isolateBidi(context.getString(R.string.Recipient_unknown));
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
public @NonNull String getShortDisplayName(@NonNull Context context) {
|
|
String name = Util.getFirstNonEmpty(getGroupName(context),
|
|
getSystemProfileName().getGivenName(),
|
|
getProfileName().getGivenName(),
|
|
getDisplayName(context));
|
|
|
|
return StringUtil.isolateBidi(name);
|
|
}
|
|
|
|
public @NonNull String getShortDisplayNameIncludingUsername(@NonNull Context context) {
|
|
String name = Util.getFirstNonEmpty(getGroupName(context),
|
|
getSystemProfileName().getGivenName(),
|
|
getProfileName().getGivenName(),
|
|
getE164().orElse(null),
|
|
getUsername().orElse(null),
|
|
getDisplayName(context));
|
|
|
|
return StringUtil.isolateBidi(name);
|
|
}
|
|
|
|
public @NonNull Optional<ServiceId> getServiceId() {
|
|
return Optional.ofNullable(serviceId);
|
|
}
|
|
|
|
public @NonNull Optional<PNI> getPni() {
|
|
return Optional.ofNullable(pni);
|
|
}
|
|
|
|
public @NonNull Optional<String> getUsername() {
|
|
if (FeatureFlags.usernames()) {
|
|
return Optional.ofNullable(username);
|
|
} else {
|
|
return Optional.empty();
|
|
}
|
|
}
|
|
|
|
public @NonNull Optional<String> getE164() {
|
|
return Optional.ofNullable(e164);
|
|
}
|
|
|
|
public @NonNull Optional<String> getEmail() {
|
|
return Optional.ofNullable(email);
|
|
}
|
|
|
|
public @NonNull Optional<GroupId> getGroupId() {
|
|
return Optional.ofNullable(groupId);
|
|
}
|
|
|
|
public @NonNull Optional<DistributionListId> getDistributionListId() {
|
|
return Optional.ofNullable(distributionListId);
|
|
}
|
|
|
|
public @NonNull Optional<String> getSmsAddress() {
|
|
return OptionalUtil.or(Optional.ofNullable(e164), Optional.ofNullable(email));
|
|
}
|
|
|
|
public @NonNull PNI requirePni() {
|
|
PNI resolved = resolving ? resolve().pni : pni;
|
|
|
|
if (resolved == null) {
|
|
throw new MissingAddressError(id);
|
|
}
|
|
|
|
return resolved;
|
|
}
|
|
|
|
public @NonNull String requireE164() {
|
|
String resolved = resolving ? resolve().e164 : e164;
|
|
|
|
if (resolved == null) {
|
|
throw new MissingAddressError(id);
|
|
}
|
|
|
|
return resolved;
|
|
}
|
|
|
|
public @NonNull String requireEmail() {
|
|
String resolved = resolving ? resolve().email : email;
|
|
|
|
if (resolved == null) {
|
|
throw new MissingAddressError(id);
|
|
}
|
|
|
|
return resolved;
|
|
}
|
|
|
|
public @NonNull String requireSmsAddress() {
|
|
Recipient recipient = resolving ? resolve() : this;
|
|
|
|
if (recipient.getE164().isPresent()) {
|
|
return recipient.getE164().get();
|
|
} else if (recipient.getEmail().isPresent()) {
|
|
return recipient.getEmail().get();
|
|
} else {
|
|
throw new MissingAddressError(id);
|
|
}
|
|
}
|
|
|
|
public boolean hasSmsAddress() {
|
|
return OptionalUtil.or(getE164(), getEmail()).isPresent();
|
|
}
|
|
|
|
public boolean hasE164() {
|
|
return getE164().isPresent();
|
|
}
|
|
|
|
public boolean hasServiceId() {
|
|
return getServiceId().isPresent();
|
|
}
|
|
|
|
public boolean isServiceIdOnly() {
|
|
return hasServiceId() && !hasSmsAddress();
|
|
}
|
|
|
|
public boolean shouldHideStory() {
|
|
return extras.map(Extras::hideStory).orElse(false);
|
|
}
|
|
|
|
public boolean hasViewedStory() {
|
|
return extras.map(Extras::hasViewedStory).orElse(false);
|
|
}
|
|
|
|
public @NonNull GroupId requireGroupId() {
|
|
GroupId resolved = resolving ? resolve().groupId : groupId;
|
|
|
|
if (resolved == null) {
|
|
throw new MissingAddressError(id);
|
|
}
|
|
|
|
return resolved;
|
|
}
|
|
|
|
public @NonNull DistributionListId requireDistributionListId() {
|
|
DistributionListId resolved = resolving ? resolve().distributionListId : distributionListId;
|
|
|
|
if (resolved == null) {
|
|
throw new MissingAddressError(id);
|
|
}
|
|
|
|
return resolved;
|
|
}
|
|
|
|
/**
|
|
* The {@link ServiceId} of the user if available, otherwise throw.
|
|
*/
|
|
public @NonNull ServiceId requireServiceId() {
|
|
ServiceId resolved = resolving ? resolve().serviceId : serviceId;
|
|
|
|
if (resolved == null) {
|
|
throw new MissingAddressError(id);
|
|
}
|
|
|
|
return resolved;
|
|
}
|
|
|
|
/**
|
|
* @return A single string to represent the recipient, in order of precedence:
|
|
*
|
|
* Group ID > ServiceId > Phone > Email
|
|
*/
|
|
public @NonNull String requireStringId() {
|
|
Recipient resolved = resolving ? resolve() : this;
|
|
|
|
if (resolved.isGroup()) {
|
|
return resolved.requireGroupId().toString();
|
|
} else if (resolved.getServiceId().isPresent()) {
|
|
return resolved.requireServiceId().toString();
|
|
}
|
|
|
|
return requireSmsAddress();
|
|
}
|
|
|
|
public Optional<Integer> getDefaultSubscriptionId() {
|
|
return defaultSubscriptionId;
|
|
}
|
|
|
|
public @NonNull ProfileName getProfileName() {
|
|
return signalProfileName;
|
|
}
|
|
|
|
private @NonNull ProfileName getSystemProfileName() {
|
|
return systemProfileName;
|
|
}
|
|
|
|
public @Nullable String getProfileAvatar() {
|
|
return profileAvatar;
|
|
}
|
|
|
|
public @NonNull ProfileAvatarFileDetails getProfileAvatarFileDetails() {
|
|
return profileAvatarFileDetails;
|
|
}
|
|
|
|
public boolean isProfileSharing() {
|
|
return profileSharing;
|
|
}
|
|
|
|
public long getLastProfileFetchTime() {
|
|
return lastProfileFetch;
|
|
}
|
|
|
|
public boolean isGroup() {
|
|
return resolve().groupId != null;
|
|
}
|
|
|
|
private boolean isGroupInternal() {
|
|
return groupId != null;
|
|
}
|
|
|
|
public boolean isMmsGroup() {
|
|
GroupId groupId = resolve().groupId;
|
|
return groupId != null && groupId.isMms();
|
|
}
|
|
|
|
public boolean isPushGroup() {
|
|
GroupId groupId = resolve().groupId;
|
|
return groupId != null && groupId.isPush();
|
|
}
|
|
|
|
public boolean isPushV1Group() {
|
|
GroupId groupId = resolve().groupId;
|
|
return groupId != null && groupId.isV1();
|
|
}
|
|
|
|
public boolean isPushV2Group() {
|
|
GroupId groupId = resolve().groupId;
|
|
return groupId != null && groupId.isV2();
|
|
}
|
|
|
|
public boolean isDistributionList() {
|
|
return resolve().distributionListId != null;
|
|
}
|
|
|
|
public boolean isMyStory() {
|
|
return Objects.equals(resolve().distributionListId, DistributionListId.from(DistributionListId.MY_STORY_ID));
|
|
}
|
|
|
|
public boolean isActiveGroup() {
|
|
RecipientId selfId = Recipient.self().getId();
|
|
return Stream.of(getParticipantIds()).anyMatch(p -> p.equals(selfId));
|
|
}
|
|
|
|
public boolean isInactiveGroup() {
|
|
return isGroup() && !isActiveGroup();
|
|
}
|
|
|
|
public @NonNull List<RecipientId> getParticipantIds() {
|
|
return new ArrayList<>(participantIds);
|
|
}
|
|
|
|
public @NonNull Drawable getFallbackContactPhotoDrawable(Context context, boolean inverted) {
|
|
return getFallbackContactPhotoDrawable(context, inverted, DEFAULT_FALLBACK_PHOTO_PROVIDER, AvatarUtil.UNDEFINED_SIZE);
|
|
}
|
|
|
|
public @NonNull Drawable getSmallFallbackContactPhotoDrawable(Context context, boolean inverted) {
|
|
return getSmallFallbackContactPhotoDrawable(context, inverted, DEFAULT_FALLBACK_PHOTO_PROVIDER);
|
|
}
|
|
|
|
public @NonNull Drawable getFallbackContactPhotoDrawable(Context context, boolean inverted, @Nullable FallbackPhotoProvider fallbackPhotoProvider, int targetSize) {
|
|
return getFallbackContactPhoto(Util.firstNonNull(fallbackPhotoProvider, DEFAULT_FALLBACK_PHOTO_PROVIDER), targetSize).asDrawable(context, avatarColor, inverted);
|
|
}
|
|
|
|
public @NonNull Drawable getSmallFallbackContactPhotoDrawable(Context context, boolean inverted, @Nullable FallbackPhotoProvider fallbackPhotoProvider) {
|
|
return getSmallFallbackContactPhotoDrawable(context, inverted, fallbackPhotoProvider, AvatarUtil.UNDEFINED_SIZE);
|
|
}
|
|
|
|
public @NonNull Drawable getSmallFallbackContactPhotoDrawable(Context context, boolean inverted, @Nullable FallbackPhotoProvider fallbackPhotoProvider, int targetSize) {
|
|
return getFallbackContactPhoto(Util.firstNonNull(fallbackPhotoProvider, DEFAULT_FALLBACK_PHOTO_PROVIDER), targetSize).asSmallDrawable(context, avatarColor, inverted);
|
|
}
|
|
|
|
public @NonNull FallbackContactPhoto getFallbackContactPhoto() {
|
|
return getFallbackContactPhoto(DEFAULT_FALLBACK_PHOTO_PROVIDER);
|
|
}
|
|
|
|
public @NonNull FallbackContactPhoto getFallbackContactPhoto(@NonNull FallbackPhotoProvider fallbackPhotoProvider) {
|
|
return getFallbackContactPhoto(fallbackPhotoProvider, AvatarUtil.UNDEFINED_SIZE);
|
|
}
|
|
|
|
public @NonNull FallbackContactPhoto getFallbackContactPhoto(@NonNull FallbackPhotoProvider fallbackPhotoProvider, int targetSize) {
|
|
if (isSelf) return fallbackPhotoProvider.getPhotoForLocalNumber();
|
|
else if (isResolving()) return fallbackPhotoProvider.getPhotoForResolvingRecipient();
|
|
else if (isDistributionList()) return fallbackPhotoProvider.getPhotoForDistributionList();
|
|
else if (isGroupInternal()) return fallbackPhotoProvider.getPhotoForGroup();
|
|
else if (isGroup()) return fallbackPhotoProvider.getPhotoForGroup();
|
|
else if (!TextUtils.isEmpty(groupName)) return fallbackPhotoProvider.getPhotoForRecipientWithName(groupName, targetSize);
|
|
else if (!TextUtils.isEmpty(systemContactName)) return fallbackPhotoProvider.getPhotoForRecipientWithName(systemContactName, targetSize);
|
|
else if (!signalProfileName.isEmpty()) return fallbackPhotoProvider.getPhotoForRecipientWithName(signalProfileName.toString(), targetSize);
|
|
else return fallbackPhotoProvider.getPhotoForRecipientWithoutName();
|
|
}
|
|
|
|
public @Nullable ContactPhoto getContactPhoto() {
|
|
if (isSelf) return null;
|
|
else if (isGroupInternal() && groupAvatarId.isPresent()) return new GroupRecordContactPhoto(groupId, groupAvatarId.get());
|
|
else if (systemContactPhoto != null && SignalStore.settings().isPreferSystemContactPhotos()) return new SystemContactPhoto(id, systemContactPhoto, 0);
|
|
else if (profileAvatar != null && profileAvatarFileDetails.hasFile()) return new ProfileContactPhoto(this);
|
|
else if (systemContactPhoto != null) return new SystemContactPhoto(id, systemContactPhoto, 0);
|
|
else return null;
|
|
}
|
|
|
|
public @Nullable Uri getMessageRingtone() {
|
|
if (messageRingtone != null && messageRingtone.getScheme() != null && messageRingtone.getScheme().startsWith("file")) {
|
|
return null;
|
|
}
|
|
|
|
return messageRingtone;
|
|
}
|
|
|
|
public @Nullable Uri getCallRingtone() {
|
|
if (callRingtone != null && callRingtone.getScheme() != null && callRingtone.getScheme().startsWith("file")) {
|
|
return null;
|
|
}
|
|
|
|
return callRingtone;
|
|
}
|
|
|
|
public boolean isMuted() {
|
|
return System.currentTimeMillis() <= muteUntil;
|
|
}
|
|
|
|
public long getMuteUntil() {
|
|
return muteUntil;
|
|
}
|
|
|
|
public boolean isBlocked() {
|
|
return blocked;
|
|
}
|
|
|
|
public @NonNull VibrateState getMessageVibrate() {
|
|
return messageVibrate;
|
|
}
|
|
|
|
public @NonNull VibrateState getCallVibrate() {
|
|
return callVibrate;
|
|
}
|
|
|
|
public int getExpiresInSeconds() {
|
|
return expireMessages;
|
|
}
|
|
|
|
public boolean hasSeenFirstInviteReminder() {
|
|
return insightsBannerTier.seen(InsightsBannerTier.TIER_ONE);
|
|
}
|
|
|
|
public boolean hasSeenSecondInviteReminder() {
|
|
return insightsBannerTier.seen(InsightsBannerTier.TIER_TWO);
|
|
}
|
|
|
|
public @NonNull RegisteredState getRegistered() {
|
|
if (isPushGroup() || isDistributionList()) {
|
|
return RegisteredState.REGISTERED;
|
|
} else if (isMmsGroup()) {
|
|
return RegisteredState.NOT_REGISTERED;
|
|
} else {
|
|
return registered;
|
|
}
|
|
}
|
|
|
|
public boolean isRegistered() {
|
|
return getRegistered() == RegisteredState.REGISTERED;
|
|
}
|
|
|
|
public boolean isMaybeRegistered() {
|
|
return getRegistered() != RegisteredState.NOT_REGISTERED;
|
|
}
|
|
|
|
public boolean isUnregistered() {
|
|
return getRegistered() == RegisteredState.NOT_REGISTERED;
|
|
}
|
|
|
|
public @Nullable String getNotificationChannel() {
|
|
return !NotificationChannels.supported() ? null : notificationChannel;
|
|
}
|
|
|
|
public boolean isForceSmsSelection() {
|
|
return forceSmsSelection;
|
|
}
|
|
|
|
public @NonNull Capability getStoriesCapability() {
|
|
return capabilities.getStoriesCapability();
|
|
}
|
|
|
|
public @NonNull Capability getGiftBadgesCapability() {
|
|
return capabilities.getGiftBadgesCapability();
|
|
}
|
|
|
|
public @NonNull Capability getPnpCapability() {
|
|
return capabilities.getPnpCapability();
|
|
}
|
|
|
|
public @NonNull Capability getPaymentActivationCapability() {
|
|
return capabilities.getPaymentActivation();
|
|
}
|
|
|
|
public @Nullable byte[] getProfileKey() {
|
|
return profileKey;
|
|
}
|
|
|
|
public @Nullable ExpiringProfileKeyCredential getExpiringProfileKeyCredential() {
|
|
return expiringProfileKeyCredential;
|
|
}
|
|
|
|
public @Nullable byte[] getStorageServiceId() {
|
|
return storageId;
|
|
}
|
|
|
|
public @NonNull UnidentifiedAccessMode getUnidentifiedAccessMode() {
|
|
if (getPni().isPresent() && getPni().equals(getServiceId())) {
|
|
return UnidentifiedAccessMode.DISABLED;
|
|
} else {
|
|
return unidentifiedAccessMode;
|
|
}
|
|
}
|
|
|
|
public @Nullable ChatWallpaper getWallpaper() {
|
|
if (wallpaper != null) {
|
|
return wallpaper;
|
|
} else if (isReleaseNotes()) {
|
|
return null;
|
|
} else {
|
|
return SignalStore.wallpaper().getWallpaper();
|
|
}
|
|
}
|
|
|
|
public boolean hasOwnWallpaper() {
|
|
return wallpaper != null;
|
|
}
|
|
|
|
/**
|
|
* A cheap way to check if wallpaper is set without doing any unnecessary proto parsing.
|
|
*/
|
|
public boolean hasWallpaper() {
|
|
return wallpaper != null || SignalStore.wallpaper().hasWallpaperSet();
|
|
}
|
|
|
|
public boolean hasOwnChatColors() {
|
|
return chatColors != null;
|
|
}
|
|
|
|
public @NonNull ChatColors getChatColors() {
|
|
if (chatColors != null && !(chatColors.getId() instanceof ChatColors.Id.Auto)) {
|
|
return chatColors;
|
|
} if (chatColors != null) {
|
|
return getAutoChatColor();
|
|
} else {
|
|
ChatColors global = SignalStore.chatColorsValues().getChatColors();
|
|
if (global != null && !(global.getId() instanceof ChatColors.Id.Auto)) {
|
|
return global;
|
|
} else {
|
|
return getAutoChatColor();
|
|
}
|
|
}
|
|
}
|
|
|
|
private @NonNull ChatColors getAutoChatColor() {
|
|
if (getWallpaper() != null) {
|
|
return getWallpaper().getAutoChatColors();
|
|
} else {
|
|
return ChatColorsPalette.Bubbles.getDefault().withId(ChatColors.Id.Auto.INSTANCE);
|
|
}
|
|
}
|
|
|
|
public @NonNull AvatarColor getAvatarColor() {
|
|
return avatarColor;
|
|
}
|
|
|
|
public boolean isSystemContact() {
|
|
return contactUri != null;
|
|
}
|
|
|
|
public @Nullable String getAbout() {
|
|
return about;
|
|
}
|
|
|
|
public @Nullable String getAboutEmoji() {
|
|
return aboutEmoji;
|
|
}
|
|
|
|
public @NonNull List<Badge> getBadges() {
|
|
return badges;
|
|
}
|
|
|
|
public @Nullable Badge getFeaturedBadge() {
|
|
if (getBadges().isEmpty()) {
|
|
return null;
|
|
} else {
|
|
return getBadges().get(0);
|
|
}
|
|
}
|
|
|
|
public @Nullable String getCombinedAboutAndEmoji() {
|
|
if (!Util.isEmpty(aboutEmoji)) {
|
|
if (!Util.isEmpty(about)) {
|
|
return aboutEmoji + " " + about;
|
|
} else {
|
|
return aboutEmoji;
|
|
}
|
|
} else if (!Util.isEmpty(about)) {
|
|
return about;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public boolean shouldBlurAvatar() {
|
|
boolean showOverride = false;
|
|
if (extras.isPresent()) {
|
|
showOverride = extras.get().manuallyShownAvatar();
|
|
}
|
|
return !showOverride && !isSelf() && !isProfileSharing() && !isSystemContact() && !hasGroupsInCommon && isRegistered();
|
|
}
|
|
|
|
public boolean hasGroupsInCommon() {
|
|
return hasGroupsInCommon;
|
|
}
|
|
|
|
/**
|
|
* If this recipient is missing crucial data, this will return a populated copy. Otherwise it
|
|
* returns itself.
|
|
*/
|
|
public @NonNull Recipient resolve() {
|
|
if (resolving) {
|
|
return live().resolve();
|
|
} else {
|
|
return this;
|
|
}
|
|
}
|
|
|
|
public boolean isResolving() {
|
|
return resolving;
|
|
}
|
|
|
|
/**
|
|
* Forces retrieving a fresh copy of the recipient, regardless of its state.
|
|
*/
|
|
public @NonNull Recipient fresh() {
|
|
return live().resolve();
|
|
}
|
|
|
|
public @NonNull LiveRecipient live() {
|
|
return ApplicationDependencies.getRecipientCache().getLive(id);
|
|
}
|
|
|
|
public @NonNull MentionSetting getMentionSetting() {
|
|
return mentionSetting;
|
|
}
|
|
|
|
public boolean isReleaseNotes() {
|
|
return isReleaseNotesRecipient;
|
|
}
|
|
|
|
public boolean showVerified() {
|
|
return isReleaseNotesRecipient || isSelf;
|
|
}
|
|
|
|
public boolean needsPniSignature() {
|
|
return FeatureFlags.phoneNumberPrivacy() && needsPniSignature;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
if (this == o) return true;
|
|
if (o == null || getClass() != o.getClass()) return false;
|
|
Recipient recipient = (Recipient) o;
|
|
return id.equals(recipient.id);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return Objects.hash(id);
|
|
}
|
|
|
|
public enum Capability {
|
|
UNKNOWN(0),
|
|
SUPPORTED(1),
|
|
NOT_SUPPORTED(2);
|
|
|
|
private final int value;
|
|
|
|
Capability(int value) {
|
|
this.value = value;
|
|
}
|
|
|
|
public int serialize() {
|
|
return value;
|
|
}
|
|
|
|
public boolean isSupported() {
|
|
return this == SUPPORTED;
|
|
}
|
|
|
|
public static Capability deserialize(int value) {
|
|
switch (value) {
|
|
case 0: return UNKNOWN;
|
|
case 1: return SUPPORTED;
|
|
case 2: return NOT_SUPPORTED;
|
|
default: throw new IllegalArgumentException();
|
|
}
|
|
}
|
|
|
|
public static Capability fromBoolean(boolean supported) {
|
|
return supported ? SUPPORTED : NOT_SUPPORTED;
|
|
}
|
|
}
|
|
|
|
public static final class Extras {
|
|
private final RecipientExtras recipientExtras;
|
|
|
|
public static @Nullable Extras from(@Nullable RecipientExtras recipientExtras) {
|
|
if (recipientExtras != null) {
|
|
return new Extras(recipientExtras);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private Extras(@NonNull RecipientExtras extras) {
|
|
this.recipientExtras = extras;
|
|
}
|
|
|
|
public boolean manuallyShownAvatar() {
|
|
return recipientExtras.getManuallyShownAvatar();
|
|
}
|
|
|
|
public boolean hideStory() {
|
|
return recipientExtras.getHideStory();
|
|
}
|
|
|
|
public boolean hasViewedStory() {
|
|
return recipientExtras.getLastStoryView() > 0L;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
if (this == o) return true;
|
|
if (o == null || getClass() != o.getClass()) return false;
|
|
final Extras that = (Extras) o;
|
|
return manuallyShownAvatar() == that.manuallyShownAvatar() && hideStory() == that.hideStory() && hasViewedStory() == that.hasViewedStory();
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return Objects.hash(manuallyShownAvatar(), hideStory(), hasViewedStory());
|
|
}
|
|
}
|
|
|
|
public boolean hasSameContent(@NonNull Recipient other) {
|
|
return Objects.equals(id, other.id) &&
|
|
resolving == other.resolving &&
|
|
isSelf == other.isSelf &&
|
|
blocked == other.blocked &&
|
|
muteUntil == other.muteUntil &&
|
|
expireMessages == other.expireMessages &&
|
|
Objects.equals(profileAvatarFileDetails, other.profileAvatarFileDetails) &&
|
|
profileSharing == other.profileSharing &&
|
|
lastProfileFetch == other.lastProfileFetch &&
|
|
forceSmsSelection == other.forceSmsSelection &&
|
|
Objects.equals(serviceId, other.serviceId) &&
|
|
Objects.equals(username, other.username) &&
|
|
Objects.equals(e164, other.e164) &&
|
|
Objects.equals(email, other.email) &&
|
|
Objects.equals(groupId, other.groupId) &&
|
|
Objects.equals(participantIds, other.participantIds) &&
|
|
Objects.equals(groupAvatarId, other.groupAvatarId) &&
|
|
messageVibrate == other.messageVibrate &&
|
|
callVibrate == other.callVibrate &&
|
|
Objects.equals(messageRingtone, other.messageRingtone) &&
|
|
Objects.equals(callRingtone, other.callRingtone) &&
|
|
Objects.equals(defaultSubscriptionId, other.defaultSubscriptionId) &&
|
|
registered == other.registered &&
|
|
Arrays.equals(profileKey, other.profileKey) &&
|
|
Objects.equals(expiringProfileKeyCredential, other.expiringProfileKeyCredential) &&
|
|
Objects.equals(groupName, other.groupName) &&
|
|
Objects.equals(systemContactPhoto, other.systemContactPhoto) &&
|
|
Objects.equals(customLabel, other.customLabel) &&
|
|
Objects.equals(contactUri, other.contactUri) &&
|
|
Objects.equals(signalProfileName, other.signalProfileName) &&
|
|
Objects.equals(systemProfileName, other.systemProfileName) &&
|
|
Objects.equals(profileAvatar, other.profileAvatar) &&
|
|
Objects.equals(notificationChannel, other.notificationChannel) &&
|
|
unidentifiedAccessMode == other.unidentifiedAccessMode &&
|
|
insightsBannerTier == other.insightsBannerTier &&
|
|
Arrays.equals(storageId, other.storageId) &&
|
|
mentionSetting == other.mentionSetting &&
|
|
Objects.equals(wallpaper, other.wallpaper) &&
|
|
Objects.equals(chatColors, other.chatColors) &&
|
|
Objects.equals(avatarColor, other.avatarColor) &&
|
|
Objects.equals(about, other.about) &&
|
|
Objects.equals(aboutEmoji, other.aboutEmoji) &&
|
|
Objects.equals(extras, other.extras) &&
|
|
hasGroupsInCommon == other.hasGroupsInCommon &&
|
|
Objects.equals(badges, other.badges);
|
|
}
|
|
|
|
private static boolean allContentsAreTheSame(@NonNull List<Recipient> a, @NonNull List<Recipient> b) {
|
|
if (a.size() != b.size()) {
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0, len = a.size(); i < len; i++) {
|
|
if (!a.get(i).hasSameContent(b.get(i))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
public static class FallbackPhotoProvider {
|
|
public @NonNull FallbackContactPhoto getPhotoForLocalNumber() {
|
|
return new ResourceContactPhoto(R.drawable.ic_note_34, R.drawable.ic_note_24);
|
|
}
|
|
|
|
public @NonNull FallbackContactPhoto getPhotoForResolvingRecipient() {
|
|
return new TransparentContactPhoto();
|
|
}
|
|
|
|
public @NonNull FallbackContactPhoto getPhotoForGroup() {
|
|
return new ResourceContactPhoto(R.drawable.ic_group_outline_34, R.drawable.ic_group_outline_20, R.drawable.ic_group_outline_48);
|
|
}
|
|
|
|
public @NonNull FallbackContactPhoto getPhotoForRecipientWithName(String name, int targetSize) {
|
|
return new GeneratedContactPhoto(name, R.drawable.ic_profile_outline_40, targetSize);
|
|
}
|
|
|
|
public @NonNull FallbackContactPhoto getPhotoForRecipientWithoutName() {
|
|
return new ResourceContactPhoto(R.drawable.ic_profile_outline_40, R.drawable.ic_profile_outline_20, R.drawable.ic_profile_outline_48);
|
|
}
|
|
|
|
public @NonNull FallbackContactPhoto getPhotoForDistributionList() {
|
|
return new ResourceContactPhoto(R.drawable.symbol_stories_24, R.drawable.symbol_stories_24, R.drawable.symbol_stories_24);
|
|
}
|
|
}
|
|
|
|
private static class MissingAddressError extends AssertionError {
|
|
MissingAddressError(@NonNull RecipientId recipientId) {
|
|
super("Missing address for " + recipientId.serialize());
|
|
}
|
|
}
|
|
}
|