+ public Profile getRecipientProfile(RecipientId recipientId) {
+ return getRecipientProfile(recipientId, false);
+ }
+
+ public void refreshRecipientProfile(RecipientId recipientId) {
+ getRecipientProfile(recipientId, true);
+ }
+
+ public ProfileKeyCredential getRecipientProfileKeyCredential(RecipientId recipientId) {
+ var profileKeyCredential = account.getProfileStore().getProfileKeyCredential(recipientId);
+ if (profileKeyCredential != null) {
+ return profileKeyCredential;
+ }
+
+ ProfileAndCredential profileAndCredential;
+ try {
+ profileAndCredential = retrieveProfileAndCredential(recipientId,
+ SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL);
+ } catch (IOException e) {
+ logger.warn("Failed to retrieve profile key credential, ignoring: {}", e.getMessage());
+ return null;
+ }
+
+ profileKeyCredential = profileAndCredential.getProfileKeyCredential().orNull();
+ account.getProfileStore().storeProfileKeyCredential(recipientId, profileKeyCredential);
+
+ var profileKey = account.getProfileStore().getProfileKey(recipientId);
+ if (profileKey != null) {
+ final var profile = decryptProfileAndDownloadAvatar(recipientId,
+ profileKey,
+ profileAndCredential.getProfile());
+ account.getProfileStore().storeProfile(recipientId, profile);
+ }
+
+ return profileKeyCredential;
+ }
+
+ /**
+ * @param givenName if null, the previous givenName will be kept
+ * @param familyName if null, the previous familyName will be kept
+ * @param about if null, the previous about text will be kept
+ * @param aboutEmoji if null, the previous about emoji will be kept
+ * @param avatar if avatar is null the image from the local avatar store is used (if present),
+ */
+ public void setProfile(
+ String givenName, final String familyName, String about, String aboutEmoji, Optional<File> avatar
+ ) throws IOException {
+ var profile = getRecipientProfile(account.getSelfRecipientId());
+ var builder = profile == null ? Profile.newBuilder() : Profile.newBuilder(profile);
+ if (givenName != null) {
+ builder.withGivenName(givenName);
+ }
+ if (familyName != null) {
+ builder.withFamilyName(familyName);
+ }
+ if (about != null) {
+ builder.withAbout(about);
+ }
+ if (aboutEmoji != null) {
+ builder.withAboutEmoji(aboutEmoji);
+ }
+ var newProfile = builder.build();
+
+ try (final var streamDetails = avatar == null
+ ? avatarStore.retrieveProfileAvatar(account.getSelfAddress())
+ : avatar.isPresent() ? Utils.createStreamDetailsFromFile(avatar.get()) : null) {
+ dependencies.getAccountManager()
+ .setVersionedProfile(account.getUuid(),
+ account.getProfileKey(),
+ newProfile.getInternalServiceName(),
+ newProfile.getAbout() == null ? "" : newProfile.getAbout(),
+ newProfile.getAboutEmoji() == null ? "" : newProfile.getAboutEmoji(),
+ Optional.absent(),
+ streamDetails);
+ }
+
+ if (avatar != null) {
+ if (avatar.isPresent()) {
+ avatarStore.storeProfileAvatar(account.getSelfAddress(),
+ outputStream -> IOUtils.copyFileToStream(avatar.get(), outputStream));
+ } else {
+ avatarStore.deleteProfileAvatar(account.getSelfAddress());
+ }
+ }
+ account.getProfileStore().storeProfile(account.getSelfRecipientId(), newProfile);
+ }
+
+ private final Set<RecipientId> pendingProfileRequest = new HashSet<>();
+
+ private Profile getRecipientProfile(RecipientId recipientId, boolean force) {
+ var profile = account.getProfileStore().getProfile(recipientId);
+
+ var now = System.currentTimeMillis();
+ // Profiles are cached for 24h before retrieving them again, unless forced
+ if (!force && profile != null && now - profile.getLastUpdateTimestamp() < 24 * 60 * 60 * 1000) {
+ return profile;
+ }
+
+ synchronized (pendingProfileRequest) {
+ if (pendingProfileRequest.contains(recipientId)) {
+ return profile;
+ }
+ pendingProfileRequest.add(recipientId);
+ }
+ final SignalServiceProfile encryptedProfile;
+ try {
+ encryptedProfile = retrieveEncryptedProfile(recipientId);
+ } finally {
+ synchronized (pendingProfileRequest) {
+ pendingProfileRequest.remove(recipientId);
+ }
+ }
+ if (encryptedProfile == null) {
+ return null;
+ }
+
+ profile = decryptProfileIfKeyKnown(recipientId, encryptedProfile);
+ account.getProfileStore().storeProfile(recipientId, profile);
+
+ return profile;
+ }
+
+ private Profile decryptProfileIfKeyKnown(
+ final RecipientId recipientId, final SignalServiceProfile encryptedProfile
+ ) {
+ var profileKey = account.getProfileStore().getProfileKey(recipientId);
+ if (profileKey == null) {
+ return new Profile(System.currentTimeMillis(),
+ null,
+ null,
+ null,
+ null,
+ ProfileUtils.getUnidentifiedAccessMode(encryptedProfile, null),
+ ProfileUtils.getCapabilities(encryptedProfile));
+ }
+
+ return decryptProfileAndDownloadAvatar(recipientId, profileKey, encryptedProfile);
+ }
+
+ private SignalServiceProfile retrieveEncryptedProfile(RecipientId recipientId) {
+ try {
+ return retrieveProfileAndCredential(recipientId, SignalServiceProfile.RequestType.PROFILE).getProfile();
+ } catch (IOException e) {
+ logger.warn("Failed to retrieve profile, ignoring: {}", e.getMessage());
+ return null;
+ }
+ }
+
+ private SignalServiceProfile retrieveProfileSync(String username) throws IOException {
+ return messageReceiverProvider.getMessageReceiver().retrieveProfileByUsername(username, Optional.absent());
+ }
+
+ private ProfileAndCredential retrieveProfileAndCredential(
+ final RecipientId recipientId, final SignalServiceProfile.RequestType requestType
+ ) throws IOException {
+ final var profileAndCredential = retrieveProfileSync(recipientId, requestType);
+ final var profile = profileAndCredential.getProfile();
+
+ try {
+ var newIdentity = account.getIdentityKeyStore()
+ .saveIdentity(recipientId,
+ new IdentityKey(Base64.getDecoder().decode(profile.getIdentityKey())),
+ new Date());
+
+ if (newIdentity) {
+ account.getSessionStore().archiveSessions(recipientId);
+ }
+ } catch (InvalidKeyException ignored) {
+ logger.warn("Got invalid identity key in profile for {}",
+ addressResolver.resolveSignalServiceAddress(recipientId).getIdentifier());
+ }
+ return profileAndCredential;
+ }
+
+ private Profile decryptProfileAndDownloadAvatar(
+ final RecipientId recipientId, final ProfileKey profileKey, final SignalServiceProfile encryptedProfile
+ ) {
+ if (encryptedProfile.getAvatar() != null) {
+ downloadProfileAvatar(addressResolver.resolveSignalServiceAddress(recipientId),
+ encryptedProfile.getAvatar(),
+ profileKey);
+ }
+
+ return ProfileUtils.decryptProfile(profileKey, encryptedProfile);
+ }
+
+ private ProfileAndCredential retrieveProfileSync(