Support additional sync behavior for linked devices.
parent
995569dd50
commit
c702ff676a
|
@ -115,7 +115,7 @@ dependencies {
|
|||
implementation 'org.conscrypt:conscrypt-android:2.0.0'
|
||||
implementation 'org.signal:aesgcmprovider:0.0.3'
|
||||
|
||||
implementation 'org.whispersystems:signal-service-android:2.15.1'
|
||||
implementation 'org.whispersystems:signal-service-android:2.15.3'
|
||||
|
||||
implementation 'org.signal:ringrtc-android:0.1.9'
|
||||
|
||||
|
|
|
@ -39,6 +39,8 @@ import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
|||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileKeyUpdateJob;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileContentUpdateJob;
|
||||
import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
|
@ -380,6 +382,7 @@ public class CreateProfileActivity extends BaseActionBarActivity {
|
|||
}
|
||||
|
||||
ApplicationDependencies.getJobManager().add(new MultiDeviceProfileKeyUpdateJob());
|
||||
ApplicationDependencies.getJobManager().add(new MultiDeviceProfileContentUpdateJob());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -48,8 +48,9 @@ public class UnidentifiedAccessUtil {
|
|||
try {
|
||||
byte[] theirUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient);
|
||||
byte[] ourUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(context);
|
||||
byte[] ourUnidentifiedAccessCertificate = recipient.resolve().isUuidSupported() ? TextSecurePreferences.getUnidentifiedAccessCertificate(context)
|
||||
: TextSecurePreferences.getUnidentifiedAccessCertificateLegacy(context) ;
|
||||
byte[] ourUnidentifiedAccessCertificate = recipient.resolve().isUuidSupported() && Recipient.self().isUuidSupported()
|
||||
? TextSecurePreferences.getUnidentifiedAccessCertificate(context)
|
||||
: TextSecurePreferences.getUnidentifiedAccessCertificateLegacy(context);
|
||||
|
||||
if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) {
|
||||
ourUnidentifiedAccessKey = Util.getSecretBytes(16);
|
||||
|
|
|
@ -16,12 +16,15 @@ import net.sqlcipher.database.SQLiteDatabase;
|
|||
|
||||
import org.thoughtcrime.securesms.color.MaterialColor;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
@ -762,6 +765,50 @@ public class RecipientDatabase extends Database {
|
|||
return databaseHelper.getReadableDatabase().query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy);
|
||||
}
|
||||
|
||||
public void applyBlockedUpdate(@NonNull List<SignalServiceAddress> blocked, List<byte[]> groupIds) {
|
||||
List<String> blockedE164 = Stream.of(blocked)
|
||||
.filter(b -> b.getNumber().isPresent())
|
||||
.map(b -> b.getNumber().get())
|
||||
.toList();
|
||||
List<String> blockedUuid = Stream.of(blocked)
|
||||
.filter(b -> b.getUuid().isPresent())
|
||||
.map(b -> b.getUuid().get().toString().toLowerCase())
|
||||
.toList();
|
||||
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
|
||||
db.beginTransaction();
|
||||
try {
|
||||
ContentValues resetBlocked = new ContentValues();
|
||||
resetBlocked.put(BLOCKED, 0);
|
||||
db.update(TABLE_NAME, resetBlocked, null, null);
|
||||
|
||||
ContentValues setBlocked = new ContentValues();
|
||||
setBlocked.put(BLOCKED, 1);
|
||||
|
||||
for (String e164 : blockedE164) {
|
||||
db.update(TABLE_NAME, setBlocked, PHONE + " = ?", new String[] { e164 });
|
||||
}
|
||||
|
||||
for (String uuid : blockedUuid) {
|
||||
db.update(TABLE_NAME, setBlocked, UUID + " = ?", new String[] { uuid });
|
||||
}
|
||||
|
||||
List<String> groupIdStrings = Stream.of(groupIds).map(g -> GroupUtil.getEncodedId(g, false)).toList();
|
||||
|
||||
for (String groupId : groupIdStrings) {
|
||||
db.update(TABLE_NAME, setBlocked, GROUP_ID + " = ?", new String[] { groupId });
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
ApplicationDependencies.getRecipientCache().clear();
|
||||
}
|
||||
|
||||
|
||||
private int update(@NonNull RecipientId id, ContentValues contentValues) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
return database.update(TABLE_NAME, contentValues, ID + " = ?", new String[] { id.serialize() });
|
||||
|
|
|
@ -51,6 +51,7 @@ public final class JobManagerFactories {
|
|||
put(MultiDeviceConfigurationUpdateJob.KEY, new MultiDeviceConfigurationUpdateJob.Factory());
|
||||
put(MultiDeviceContactUpdateJob.KEY, new MultiDeviceContactUpdateJob.Factory());
|
||||
put(MultiDeviceGroupUpdateJob.KEY, new MultiDeviceGroupUpdateJob.Factory());
|
||||
put(MultiDeviceProfileContentUpdateJob.KEY, new MultiDeviceProfileContentUpdateJob.Factory());
|
||||
put(MultiDeviceProfileKeyUpdateJob.KEY, new MultiDeviceProfileKeyUpdateJob.Factory());
|
||||
put(MultiDeviceReadUpdateJob.KEY, new MultiDeviceReadUpdateJob.Factory());
|
||||
put(MultiDeviceStickerPackOperationJob.KEY, new MultiDeviceStickerPackOperationJob.Factory());
|
||||
|
@ -64,6 +65,7 @@ public final class JobManagerFactories {
|
|||
put(PushNotificationReceiveJob.KEY, new PushNotificationReceiveJob.Factory());
|
||||
put(PushTextSendJob.KEY, new PushTextSendJob.Factory());
|
||||
put(RefreshAttributesJob.KEY, new RefreshAttributesJob.Factory());
|
||||
put(RefreshOwnProfileJob.KEY, new RefreshOwnProfileJob.Factory());
|
||||
put(RefreshPreKeysJob.KEY, new RefreshPreKeysJob.Factory());
|
||||
put(RefreshUnidentifiedDeliveryAbilityJob.KEY, new RefreshUnidentifiedDeliveryAbilityJob.Factory());
|
||||
put(RequestGroupInfoJob.KEY, new RequestGroupInfoJob.Factory());
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||
|
||||
public class MultiDeviceProfileContentUpdateJob extends BaseJob {
|
||||
|
||||
public static final String KEY = "MultiDeviceProfileContentUpdateJob";
|
||||
|
||||
private static final String TAG = Log.tag(MultiDeviceProfileContentUpdateJob.class);
|
||||
|
||||
public MultiDeviceProfileContentUpdateJob() {
|
||||
this(new Parameters.Builder()
|
||||
.setQueue("MultiDeviceProfileUpdateJob")
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.setMaxAttempts(10)
|
||||
.build());
|
||||
}
|
||||
|
||||
private MultiDeviceProfileContentUpdateJob(@NonNull Parameters parameters) {
|
||||
super(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Data serialize() {
|
||||
return Data.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRun() throws Exception {
|
||||
if (!TextSecurePreferences.isMultiDevice(context)) {
|
||||
Log.i(TAG, "Not multi device, aborting...");
|
||||
return;
|
||||
}
|
||||
|
||||
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
|
||||
|
||||
messageSender.sendMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.LOCAL_PROFILE),
|
||||
UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onShouldRetry(@NonNull Exception e) {
|
||||
return e instanceof PushNetworkException;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onCanceled() {
|
||||
Log.w(TAG, "Did not succeed!");
|
||||
}
|
||||
|
||||
public static final class Factory implements Job.Factory<MultiDeviceProfileContentUpdateJob> {
|
||||
@Override
|
||||
public @NonNull MultiDeviceProfileContentUpdateJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||
return new MultiDeviceProfileContentUpdateJob(parameters);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -114,6 +114,8 @@ import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
|||
import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.ConfigurationMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
|
||||
|
@ -268,6 +270,9 @@ public class PushDecryptJob extends BaseJob {
|
|||
else if (syncMessage.getViewOnceOpen().isPresent()) handleSynchronizeViewOnceOpenMessage(syncMessage.getViewOnceOpen().get(), content.getTimestamp());
|
||||
else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get());
|
||||
else if (syncMessage.getStickerPackOperations().isPresent()) handleSynchronizeStickerPackOperation(syncMessage.getStickerPackOperations().get());
|
||||
else if (syncMessage.getConfiguration().isPresent()) handleSynchronizeConfigurationMessage(syncMessage.getConfiguration().get());
|
||||
else if (syncMessage.getBlockedList().isPresent()) handleSynchronizeBlockedListMessage(syncMessage.getBlockedList().get());
|
||||
else if (syncMessage.getFetchType().isPresent()) handleSynchronizeFetchMessage(syncMessage.getFetchType().get());
|
||||
else Log.w(TAG, "Contains no known sync types...");
|
||||
} else if (content.getCallMessage().isPresent()) {
|
||||
Log.i(TAG, "Got call message...");
|
||||
|
@ -546,6 +551,36 @@ public class PushDecryptJob extends BaseJob {
|
|||
}
|
||||
}
|
||||
|
||||
private void handleSynchronizeConfigurationMessage(@NonNull ConfigurationMessage configurationMessage) {
|
||||
if (configurationMessage.getReadReceipts().isPresent()) {
|
||||
TextSecurePreferences.setReadReceiptsEnabled(context, configurationMessage.getReadReceipts().get());
|
||||
}
|
||||
|
||||
if (configurationMessage.getUnidentifiedDeliveryIndicators().isPresent()) {
|
||||
TextSecurePreferences.setShowUnidentifiedDeliveryIndicatorsEnabled(context, configurationMessage.getReadReceipts().get());
|
||||
}
|
||||
|
||||
if (configurationMessage.getTypingIndicators().isPresent()) {
|
||||
TextSecurePreferences.setTypingIndicatorsEnabled(context, configurationMessage.getTypingIndicators().get());
|
||||
}
|
||||
|
||||
if (configurationMessage.getLinkPreviews().isPresent()) {
|
||||
TextSecurePreferences.setLinkPreviewsEnabled(context, configurationMessage.getReadReceipts().get());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSynchronizeBlockedListMessage(@NonNull BlockedListMessage blockMessage) {
|
||||
DatabaseFactory.getRecipientDatabase(context).applyBlockedUpdate(blockMessage.getAddresses(), blockMessage.getGroupIds());
|
||||
}
|
||||
|
||||
private void handleSynchronizeFetchMessage(@NonNull SignalServiceSyncMessage.FetchType fetchType) {
|
||||
if (fetchType == SignalServiceSyncMessage.FetchType.LOCAL_PROFILE) {
|
||||
ApplicationDependencies.getJobManager().add(new RefreshOwnProfileJob());
|
||||
} else {
|
||||
Log.w(TAG, "Received a fetch message for an unknown type.");
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSynchronizeSentMessage(@NonNull SignalServiceContent content,
|
||||
@NonNull SentTranscriptMessage message)
|
||||
throws StorageFailedException
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.ProfileUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Refreshes the profile of the local user. Different from {@link RetrieveProfileJob} in that we
|
||||
* have to sometimes look at/set different data stores, and we will *always* do the fetch regardless
|
||||
* of caching.
|
||||
*/
|
||||
public class RefreshOwnProfileJob extends BaseJob {
|
||||
|
||||
public static final String KEY = "RefreshOwnProfileJob";
|
||||
|
||||
private static final String TAG = Log.tag(RefreshOwnProfileJob.class);
|
||||
|
||||
public RefreshOwnProfileJob() {
|
||||
this(new Parameters.Builder()
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.setQueue("RefreshOwnProfileJob")
|
||||
.setMaxInstances(1)
|
||||
.setMaxAttempts(10)
|
||||
.build());
|
||||
}
|
||||
|
||||
|
||||
private RefreshOwnProfileJob(@NonNull Parameters parameters) {
|
||||
super(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Data serialize() {
|
||||
return Data.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRun() throws Exception {
|
||||
SignalServiceProfile profile = ProfileUtil.retrieveProfile(context, Recipient.self());
|
||||
|
||||
setProfileName(profile.getName());
|
||||
setProfileAvatar(profile.getAvatar());
|
||||
setProfileCapabilities(profile.getCapabilities());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onShouldRetry(@NonNull Exception e) {
|
||||
return e instanceof PushNetworkException;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled() { }
|
||||
|
||||
private void setProfileName(@Nullable String encryptedName) {
|
||||
try {
|
||||
byte[] profileKey = ProfileKeyUtil.getProfileKey(context);
|
||||
String plaintextName = ProfileUtil.decryptName(profileKey, encryptedName);
|
||||
|
||||
DatabaseFactory.getRecipientDatabase(context).setProfileName(Recipient.self().getId(), plaintextName);
|
||||
TextSecurePreferences.setProfileName(context, plaintextName);
|
||||
} catch (InvalidCiphertextException | IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void setProfileAvatar(@Nullable String avatar) {
|
||||
ApplicationDependencies.getJobManager().add(new RetrieveProfileAvatarJob(Recipient.self(), avatar));
|
||||
}
|
||||
|
||||
private void setProfileCapabilities(@Nullable SignalServiceProfile.Capabilities capabilities) {
|
||||
if (capabilities == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
DatabaseFactory.getRecipientDatabase(context).setUuidSupported(Recipient.self().getId(), capabilities.isUuid());
|
||||
}
|
||||
|
||||
public static final class Factory implements Job.Factory<RefreshOwnProfileJob> {
|
||||
|
||||
@Override
|
||||
public @NonNull RefreshOwnProfileJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||
return new RefreshOwnProfileJob(parameters);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.logging.Log;
|
|||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
|
@ -117,6 +118,10 @@ public class RetrieveProfileAvatarJob extends BaseJob {
|
|||
}
|
||||
|
||||
database.setProfileAvatar(recipient.getId(), profileAvatar);
|
||||
|
||||
if (recipient.isLocalNumber()) {
|
||||
TextSecurePreferences.setProfileAvatarId(context, Util.getSecureRandom().nextInt());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -22,6 +22,8 @@ import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
|||
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||
import org.thoughtcrime.securesms.util.ProfileUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
|
@ -39,6 +41,10 @@ import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulRespons
|
|||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Retrieves a users profile and sets the appropriate local fields. If fetching the profile of the
|
||||
* local user, use {@link RefreshOwnProfileJob} instead.
|
||||
*/
|
||||
public class RetrieveProfileJob extends BaseJob {
|
||||
|
||||
public static final String KEY = "RetrieveProfileJob";
|
||||
|
@ -94,25 +100,12 @@ public class RetrieveProfileJob extends BaseJob {
|
|||
}
|
||||
|
||||
private void handlePhoneNumberRecipient(Recipient recipient) throws IOException {
|
||||
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
Optional<UnidentifiedAccess> unidentifiedAccess = getUnidentifiedAccess(recipient);
|
||||
SignalServiceProfile profile = ProfileUtil.retrieveProfile(context, recipient);
|
||||
|
||||
SignalServiceProfile profile;
|
||||
|
||||
try {
|
||||
profile = retrieveProfile(address, unidentifiedAccess);
|
||||
} catch (NonSuccessfulResponseCodeException e) {
|
||||
if (unidentifiedAccess.isPresent()) {
|
||||
profile = retrieveProfile(address, Optional.absent());
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
setIdentityKey(recipient, profile.getIdentityKey());
|
||||
setProfileName(recipient, profile.getName());
|
||||
setProfileAvatar(recipient, profile.getAvatar());
|
||||
setProfileCapabilities(recipient, profile.getCapabilities());
|
||||
setIdentityKey(recipient, profile.getIdentityKey());
|
||||
setUnidentifiedAccessMode(recipient, profile.getUnidentifiedAccess(), profile.isUnrestrictedUnidentifiedAccess());
|
||||
}
|
||||
|
||||
|
@ -124,26 +117,6 @@ public class RetrieveProfileJob extends BaseJob {
|
|||
}
|
||||
}
|
||||
|
||||
private SignalServiceProfile retrieveProfile(@NonNull SignalServiceAddress address, Optional<UnidentifiedAccess> unidentifiedAccess)
|
||||
throws IOException
|
||||
{
|
||||
SignalServiceMessagePipe authPipe = IncomingMessageObserver.getPipe();
|
||||
SignalServiceMessagePipe unidentifiedPipe = IncomingMessageObserver.getUnidentifiedPipe();
|
||||
SignalServiceMessagePipe pipe = unidentifiedPipe != null && unidentifiedAccess.isPresent() ? unidentifiedPipe
|
||||
: authPipe;
|
||||
|
||||
if (pipe != null) {
|
||||
try {
|
||||
return pipe.getProfile(address, unidentifiedAccess);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
SignalServiceMessageReceiver receiver = ApplicationDependencies.getSignalServiceMessageReceiver();
|
||||
return receiver.retrieveProfile(address, unidentifiedAccess);
|
||||
}
|
||||
|
||||
private void setIdentityKey(Recipient recipient, String identityKeyValue) {
|
||||
try {
|
||||
if (TextUtils.isEmpty(identityKeyValue)) {
|
||||
|
@ -206,12 +179,7 @@ public class RetrieveProfileJob extends BaseJob {
|
|||
byte[] profileKey = recipient.getProfileKey();
|
||||
if (profileKey == null) return;
|
||||
|
||||
String plaintextProfileName = null;
|
||||
|
||||
if (profileName != null) {
|
||||
ProfileCipher profileCipher = new ProfileCipher(profileKey);
|
||||
plaintextProfileName = new String(profileCipher.decryptName(Base64.decode(profileName)));
|
||||
}
|
||||
String plaintextProfileName = ProfileUtil.decryptName(profileKey, profileName);
|
||||
|
||||
if (!Util.equals(plaintextProfileName, recipient.getProfileName())) {
|
||||
DatabaseFactory.getRecipientDatabase(context).setProfileName(recipient.getId(), plaintextProfileName);
|
||||
|
@ -237,16 +205,6 @@ public class RetrieveProfileJob extends BaseJob {
|
|||
DatabaseFactory.getRecipientDatabase(context).setUuidSupported(recipient.getId(), capabilities.isUuid());
|
||||
}
|
||||
|
||||
private Optional<UnidentifiedAccess> getUnidentifiedAccess(@NonNull Recipient recipient) {
|
||||
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient);
|
||||
|
||||
if (unidentifiedAccess.isPresent()) {
|
||||
return unidentifiedAccess.get().getTargetUnidentifiedAccess();
|
||||
}
|
||||
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
public static final class Factory implements Job.Factory<RetrieveProfileJob> {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -126,4 +126,9 @@ public final class LiveRecipientCache {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
public synchronized void clear() {
|
||||
recipients.clear();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ import org.thoughtcrime.securesms.phonenumbers.NumberUtil;
|
|||
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.libsignal.util.guava.Preconditions;
|
||||
|
|
|
@ -8,6 +8,7 @@ import androidx.annotation.Nullable;
|
|||
|
||||
import org.thoughtcrime.securesms.color.MaterialColor;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase.InsightsBannerTier;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode;
|
||||
|
@ -82,7 +83,7 @@ public class RecipientDetails {
|
|||
this.profileName = isLocalNumber ? TextSecurePreferences.getProfileName(context) : settings.getProfileName();
|
||||
this.defaultSubscriptionId = settings.getDefaultSubscriptionId();
|
||||
this.registered = settings.getRegistered();
|
||||
this.profileKey = settings.getProfileKey();
|
||||
this.profileKey = isLocalNumber ? ProfileKeyUtil.getProfileKey(context) : settings.getProfileKey();
|
||||
this.profileAvatar = settings.getProfileAvatar();
|
||||
this.profileSharing = settings.isProfileSharing();
|
||||
this.systemContact = systemContact;
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
|
||||
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
|
||||
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Aids in the retrieval and decryption of profiles.
|
||||
*/
|
||||
public class ProfileUtil {
|
||||
|
||||
private static final String TAG = Log.tag(ProfileUtil.class);
|
||||
|
||||
@WorkerThread
|
||||
public static SignalServiceProfile retrieveProfile(@NonNull Context context, @NonNull Recipient recipient) throws IOException {
|
||||
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
Optional<UnidentifiedAccess> unidentifiedAccess = getUnidentifiedAccess(context, recipient);
|
||||
|
||||
SignalServiceProfile profile;
|
||||
|
||||
try {
|
||||
profile = retrieveProfileInternal(address, unidentifiedAccess);
|
||||
} catch (NonSuccessfulResponseCodeException e) {
|
||||
if (unidentifiedAccess.isPresent()) {
|
||||
profile = retrieveProfileInternal(address, Optional.absent());
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
public static @Nullable String decryptName(@NonNull byte[] profileKey, @Nullable String encryptedName)
|
||||
throws InvalidCiphertextException, IOException
|
||||
{
|
||||
if (encryptedName == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ProfileCipher profileCipher = new ProfileCipher(profileKey);
|
||||
return new String(profileCipher.decryptName(Base64.decode(encryptedName)));
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private static SignalServiceProfile retrieveProfileInternal(@NonNull SignalServiceAddress address, Optional<UnidentifiedAccess> unidentifiedAccess)
|
||||
throws IOException
|
||||
{
|
||||
SignalServiceMessagePipe authPipe = IncomingMessageObserver.getPipe();
|
||||
SignalServiceMessagePipe unidentifiedPipe = IncomingMessageObserver.getUnidentifiedPipe();
|
||||
SignalServiceMessagePipe pipe = unidentifiedPipe != null && unidentifiedAccess.isPresent() ? unidentifiedPipe
|
||||
: authPipe;
|
||||
|
||||
if (pipe != null) {
|
||||
try {
|
||||
return pipe.getProfile(address, unidentifiedAccess);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
SignalServiceMessageReceiver receiver = ApplicationDependencies.getSignalServiceMessageReceiver();
|
||||
return receiver.retrieveProfile(address, unidentifiedAccess);
|
||||
}
|
||||
|
||||
private static Optional<UnidentifiedAccess> getUnidentifiedAccess(@NonNull Context context, @NonNull Recipient recipient) {
|
||||
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient);
|
||||
|
||||
if (unidentifiedAccess.isPresent()) {
|
||||
return unidentifiedAccess.get().getTargetUnidentifiedAccess();
|
||||
}
|
||||
|
||||
return Optional.absent();
|
||||
}
|
||||
}
|
|
@ -386,6 +386,10 @@ public class TextSecurePreferences {
|
|||
return getBooleanPreference(context, LINK_PREVIEWS, true);
|
||||
}
|
||||
|
||||
public static void setLinkPreviewsEnabled(Context context, boolean enabled) {
|
||||
setBooleanPreference(context, LINK_PREVIEWS, enabled);
|
||||
}
|
||||
|
||||
public static boolean isGifSearchInGridLayout(Context context) {
|
||||
return getBooleanPreference(context, GIF_GRID_LAYOUT, false);
|
||||
}
|
||||
|
@ -607,6 +611,10 @@ public class TextSecurePreferences {
|
|||
return getBooleanPreference(context, UNIVERSAL_UNIDENTIFIED_ACCESS, false);
|
||||
}
|
||||
|
||||
public static void setShowUnidentifiedDeliveryIndicatorsEnabled(Context context, boolean enabled) {
|
||||
setBooleanPreference(context, SHOW_UNIDENTIFIED_DELIVERY_INDICATORS, enabled);
|
||||
}
|
||||
|
||||
public static boolean isShowUnidentifiedDeliveryIndicatorsEnabled(Context context) {
|
||||
return getBooleanPreference(context, SHOW_UNIDENTIFIED_DELIVERY_INDICATORS, false);
|
||||
}
|
||||
|
|
|
@ -375,11 +375,11 @@ dependencyVerification {
|
|||
['org.whispersystems:signal-protocol-java:2.8.1',
|
||||
'b19db36839ab008fdccefc7f8c005f2ea43dc7c7298a209bc424e6f9b6d5617b'],
|
||||
|
||||
['org.whispersystems:signal-service-android:2.15.1',
|
||||
'27c6cf63393a2d466474e45162ec254c557f6a19e91962d0e4acc870373c41fd'],
|
||||
['org.whispersystems:signal-service-android:2.15.3',
|
||||
'24e7a7760f14a261d44b8d76fe6004157f7e09d7898c88ddb501ceabea47b6f6'],
|
||||
|
||||
['org.whispersystems:signal-service-java:2.15.1',
|
||||
'd0023125e232b11ead150bbf38361fa61a3dbf3adc81c2473c673735380db9c2'],
|
||||
['org.whispersystems:signal-service-java:2.15.3',
|
||||
'bc6d58924daf7c15e700164ce602e2647260c41820a43837f3f48f3d5be2e963'],
|
||||
|
||||
['pl.tajchert:waitingdots:0.1.0',
|
||||
'2835d49e0787dbcb606c5a60021ced66578503b1e9fddcd7a5ef0cd5f095ba2c'],
|
||||
|
|
Loading…
Reference in New Issue