Multi-device profile avatar.

This commit is contained in:
Mikunj 2019-11-28 09:25:00 +11:00
parent 9337a1d44a
commit 6e7b21e8b4
7 changed files with 75 additions and 12 deletions

View File

@ -41,6 +41,9 @@ import android.widget.Toast;
import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.thoughtcrime.securesms.components.RatingManager; import org.thoughtcrime.securesms.components.RatingManager;
import org.thoughtcrime.securesms.components.SearchToolbar; import org.thoughtcrime.securesms.components.SearchToolbar;
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto; import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
@ -52,6 +55,7 @@ import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.lock.RegistrationLockDialog; import org.thoughtcrime.securesms.lock.RegistrationLockDialog;
import org.thoughtcrime.securesms.loki.AddPublicChatActivity; import org.thoughtcrime.securesms.loki.AddPublicChatActivity;
import org.thoughtcrime.securesms.loki.JazzIdenticonDrawable; import org.thoughtcrime.securesms.loki.JazzIdenticonDrawable;
import org.thoughtcrime.securesms.loki.RecipientAvatarModifiedEvent;
import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.notifications.MarkReadReceiver; import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.notifications.MessageNotifier;
@ -134,6 +138,18 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
super.onDestroy(); super.onDestroy();
} }
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
@Override @Override
public boolean onPrepareOptionsMenu(Menu menu) { public boolean onPrepareOptionsMenu(Menu menu) {
MenuInflater inflater = this.getMenuInflater(); MenuInflater inflater = this.getMenuInflater();
@ -214,7 +230,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
Drawable fallback = primaryRecipient.getFallbackContactPhotoDrawable(this, false); Drawable fallback = primaryRecipient.getFallbackContactPhotoDrawable(this, false);
GlideApp.with(this) GlideApp.with(this)
.load(new ProfileContactPhoto(primaryRecipient.getAddress(), String.valueOf(TextSecurePreferences.getProfileAvatarId(this)))) .load(primaryRecipient.getContactPhoto())
.fallback(fallback) .fallback(fallback)
.error(fallback) .error(fallback)
.diskCacheStrategy(DiskCacheStrategy.ALL) .diskCacheStrategy(DiskCacheStrategy.ALL)
@ -321,4 +337,14 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
private void addNewPublicChat() { private void addNewPublicChat() {
startActivity(new Intent(this, AddPublicChatActivity.class)); startActivity(new Intent(this, AddPublicChatActivity.class));
} }
@Subscribe(threadMode = ThreadMode.MAIN)
public void onAvatarModified(RecipientAvatarModifiedEvent event) {
Recipient recipient = event.getRecipient();
String ourMasterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(this);
boolean isOurMasterDevice = ourMasterHexEncodedPublicKey != null && ourMasterHexEncodedPublicKey.equals(recipient.getAddress().serialize());
if (recipient.isLocalNumber() || isOurMasterDevice) {
initializeProfileIcon(recipient);
}
}
} }

View File

@ -427,7 +427,7 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
return false; return false;
} }
ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceProfileKeyUpdateJob()); // ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceProfileKeyUpdateJob());
return true; return true;
} }

View File

@ -724,6 +724,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
handleUnknownGroupMessage(content, message.getMessage().getGroupInfo().get()); handleUnknownGroupMessage(content, message.getMessage().getGroupInfo().get());
} }
String ourMasterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
boolean isSenderMasterDevice = ourMasterDevice != null && ourMasterDevice.equals(content.getSender());
if (message.getMessage().getProfileKey().isPresent()) { if (message.getMessage().getProfileKey().isPresent()) {
Recipient recipient = null; Recipient recipient = null;
@ -734,6 +736,16 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (recipient != null && !recipient.isSystemContact() && !recipient.isProfileSharing()) { if (recipient != null && !recipient.isSystemContact() && !recipient.isProfileSharing()) {
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true); DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
} }
// Loki - If we received a sync message from our master device then we need to extract the avatar url
if (isSenderMasterDevice) {
handleProfileKey(content, message.getMessage());
}
}
// Loki - Update display name from master device
if (isSenderMasterDevice && content.senderDisplayName.isPresent() && content.senderDisplayName.get().length() > 0) {
TextSecurePreferences.setProfileName(context, content.senderDisplayName.get());
} }
if (threadId != null) { if (threadId != null) {
@ -1142,7 +1154,10 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (content.senderDisplayName.isPresent() && content.senderDisplayName.get().length() > 0) { if (content.senderDisplayName.isPresent() && content.senderDisplayName.get().length() > 0) {
TextSecurePreferences.setProfileName(context, content.senderDisplayName.get()); TextSecurePreferences.setProfileName(context, content.senderDisplayName.get());
} }
// Profile avatar updates
if (content.getDataMessage().isPresent()) {
handleProfileKey(content, content.getDataMessage().get());
}
// Contact sync // Contact sync
if (content.getSyncMessage().isPresent() && content.getSyncMessage().get().getContacts().isPresent()) { if (content.getSyncMessage().isPresent() && content.getSyncMessage().get().getContacts().isPresent()) {
handleSynchronizeContactMessage(content.getSyncMessage().get().getContacts().get()); handleSynchronizeContactMessage(content.getSyncMessage().get().getContacts().get());
@ -1413,8 +1428,14 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
private void handleProfileKey(@NonNull SignalServiceContent content, private void handleProfileKey(@NonNull SignalServiceContent content,
@NonNull SignalServiceDataMessage message) @NonNull SignalServiceDataMessage message)
{ {
if (!message.getProfileKey().isPresent()) { return; }
/*
If we get a profile key then we don't need to map it to the primary device.
For now a profile key is mapped one-to-one to avoid secondary devices setting the incorrect avatar for a primary device.
*/
RecipientDatabase database = DatabaseFactory.getRecipientDatabase(context); RecipientDatabase database = DatabaseFactory.getRecipientDatabase(context);
Recipient recipient = getPrimaryDeviceRecipient(content.getSender()); Recipient recipient = Recipient.from(context, Address.fromSerialized(content.getSender()), false);
if (recipient.getProfileKey() == null || !MessageDigest.isEqual(recipient.getProfileKey(), message.getProfileKey().get())) { if (recipient.getProfileKey() == null || !MessageDigest.isEqual(recipient.getProfileKey(), message.getProfileKey().get())) {
database.setProfileKey(recipient, message.getProfileKey().get()); database.setProfileKey(recipient, message.getProfileKey().get());

View File

@ -11,6 +11,8 @@ import nl.komponents.kovenant.toFailVoid
import nl.komponents.kovenant.ui.successUi import nl.komponents.kovenant.ui.successUi
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.crypto.PreKeyUtil
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
import org.thoughtcrime.securesms.database.Address import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.logging.Log import org.thoughtcrime.securesms.logging.Log
@ -114,12 +116,16 @@ fun shouldAutomaticallyBecomeFriendsWithDevice(publicKey: String, context: Conte
fun sendPairingAuthorisationMessage(context: Context, contactHexEncodedPublicKey: String, authorisation: PairingAuthorisation): Promise<Unit, Exception> { fun sendPairingAuthorisationMessage(context: Context, contactHexEncodedPublicKey: String, authorisation: PairingAuthorisation): Promise<Unit, Exception> {
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender() val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
val address = SignalServiceAddress(contactHexEncodedPublicKey) val address = SignalServiceAddress(contactHexEncodedPublicKey)
val message = SignalServiceDataMessage.newBuilder().withBody(null).withPairingAuthorisation(authorisation) val message = SignalServiceDataMessage.newBuilder().withPairingAuthorisation(authorisation)
// A REQUEST should always act as a friend request. A GRANT should always be replying back as a normal message. // A REQUEST should always act as a friend request. A GRANT should always be replying back as a normal message.
if (authorisation.type == PairingAuthorisation.Type.REQUEST) { if (authorisation.type == PairingAuthorisation.Type.REQUEST) {
val preKeyBundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(address.number) val preKeyBundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(address.number)
message.asFriendRequest(true).withPreKeyBundle(preKeyBundle) message.asFriendRequest(true).withPreKeyBundle(preKeyBundle)
} else {
// Send over our profile key so that our linked device can get our profile picture
message.withProfileKey(ProfileKeyUtil.getProfileKey(context))
} }
return try { return try {
Log.d("Loki", "Sending authorisation message to: $contactHexEncodedPublicKey.") Log.d("Loki", "Sending authorisation message to: $contactHexEncodedPublicKey.")
val result = messageSender.sendMessage(0, address, Optional.absent<UnidentifiedAccessPair>(), message.build()) val result = messageSender.sendMessage(0, address, Optional.absent<UnidentifiedAccessPair>(), message.build())

View File

@ -0,0 +1,5 @@
package org.thoughtcrime.securesms.loki
import org.thoughtcrime.securesms.recipients.Recipient
data class RecipientAvatarModifiedEvent(val recipient: Recipient)

View File

@ -20,6 +20,7 @@ import android.widget.Toast;
import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto; import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.loki.MnemonicUtilities; import org.thoughtcrime.securesms.loki.MnemonicUtilities;
@ -80,13 +81,14 @@ public class ProfilePreference extends Preference {
public void refresh() { public void refresh() {
if (profileNumberView == null) return; if (profileNumberView == null) return;
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext()); Context context = getContext();
String primaryDevicePublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(getContext()); String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
String primaryDevicePublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
String publicKey = primaryDevicePublicKey != null ? primaryDevicePublicKey : userHexEncodedPublicKey; String publicKey = primaryDevicePublicKey != null ? primaryDevicePublicKey : userHexEncodedPublicKey;
final Address localAddress = Address.fromSerialized(publicKey); final Address localAddress = Address.fromSerialized(publicKey);
final String profileName = TextSecurePreferences.getProfileName(getContext()); final Recipient recipient = Recipient.from(context, localAddress, false);
final String profileName = TextSecurePreferences.getProfileName(context);
Context context = getContext();
containerView.setOnLongClickListener(v -> { containerView.setOnLongClickListener(v -> {
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("Public Key", publicKey); ClipData clip = ClipData.newPlainText("Public Key", publicKey);
@ -104,9 +106,9 @@ public class ProfilePreference extends Preference {
}); });
avatarView.setClipToOutline(true); avatarView.setClipToOutline(true);
Drawable fallback = Recipient.from(context, localAddress, false).getFallbackContactPhotoDrawable(context, false); Drawable fallback = recipient.getFallbackContactPhotoDrawable(context, false);
GlideApp.with(getContext().getApplicationContext()) GlideApp.with(getContext().getApplicationContext())
.load(new ProfileContactPhoto(localAddress, String.valueOf(TextSecurePreferences.getProfileAvatarId(getContext())))) .load(recipient.getContactPhoto())
.fallback(fallback) .fallback(fallback)
.error(fallback) .error(fallback)
.circleCrop() .circleCrop()

View File

@ -26,6 +26,7 @@ import android.text.TextUtils;
import com.annimon.stream.function.Consumer; import com.annimon.stream.function.Consumer;
import org.greenrobot.eventbus.EventBus;
import org.thoughtcrime.securesms.color.MaterialColor; import org.thoughtcrime.securesms.color.MaterialColor;
import org.thoughtcrime.securesms.contacts.avatars.ContactColors; import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto; import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
@ -45,6 +46,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessM
import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState; import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.JazzIdenticonContactPhoto; import org.thoughtcrime.securesms.loki.JazzIdenticonContactPhoto;
import org.thoughtcrime.securesms.loki.RecipientAvatarModifiedEvent;
import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.recipients.RecipientProvider.RecipientDetails; import org.thoughtcrime.securesms.recipients.RecipientProvider.RecipientDetails;
import org.thoughtcrime.securesms.util.FutureTaskListener; import org.thoughtcrime.securesms.util.FutureTaskListener;
@ -394,6 +396,7 @@ public class Recipient implements RecipientModifiedListener {
} }
notifyListeners(); notifyListeners();
EventBus.getDefault().post(new RecipientAvatarModifiedEvent(this));
} }
public synchronized boolean isProfileSharing() { public synchronized boolean isProfileSharing() {
@ -469,7 +472,7 @@ public class Recipient implements RecipientModifiedListener {
} }
public synchronized @Nullable ContactPhoto getContactPhoto() { public synchronized @Nullable ContactPhoto getContactPhoto() {
if (isLocalNumber) return null; if (isLocalNumber) return new ProfileContactPhoto(address, String.valueOf(TextSecurePreferences.getProfileAvatarId(context)));
else if (isGroupRecipient() && groupAvatarId != null) return new GroupRecordContactPhoto(address, groupAvatarId); else if (isGroupRecipient() && groupAvatarId != null) return new GroupRecordContactPhoto(address, groupAvatarId);
else if (systemContactPhoto != null) return new SystemContactPhoto(address, systemContactPhoto, 0); else if (systemContactPhoto != null) return new SystemContactPhoto(address, systemContactPhoto, 0);
else if (profileAvatar != null) return new ProfileContactPhoto(address, profileAvatar); else if (profileAvatar != null) return new ProfileContactPhoto(address, profileAvatar);