Skip to content

Commit

Permalink
Support additional sync behavior for linked devices.
Browse files Browse the repository at this point in the history
  • Loading branch information
greyson-signal authored and alan-signal committed Nov 13, 2019
1 parent 995569d commit c702ff6
Show file tree
Hide file tree
Showing 16 changed files with 401 additions and 59 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Expand Up @@ -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'

Expand Down
3 changes: 3 additions & 0 deletions src/org/thoughtcrime/securesms/CreateProfileActivity.java
Expand Up @@ -39,6 +39,8 @@
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;
Expand Down Expand Up @@ -380,6 +382,7 @@ protected Boolean doInBackground(Void... params) {
}

ApplicationDependencies.getJobManager().add(new MultiDeviceProfileKeyUpdateJob());
ApplicationDependencies.getJobManager().add(new MultiDeviceProfileContentUpdateJob());

return true;
}
Expand Down
Expand Up @@ -48,8 +48,9 @@ public static Optional<UnidentifiedAccessPair> getAccessFor(@NonNull Context con
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);
Expand Down
47 changes: 47 additions & 0 deletions src/org/thoughtcrime/securesms/database/RecipientDatabase.java
Expand Up @@ -16,12 +16,15 @@

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;
Expand Down Expand Up @@ -762,6 +765,50 @@ public void updateSystemContactColors(@NonNull ColorUpdater updater) {
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() });
Expand Down
2 changes: 2 additions & 0 deletions src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java
Expand Up @@ -51,6 +51,7 @@ public static Map<String, Job.Factory> getJobFactories(@NonNull Application appl
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());
Expand All @@ -64,6 +65,7 @@ public static Map<String, Job.Factory> getJobFactories(@NonNull Application appl
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());
Expand Down
@@ -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);
}
}
}
35 changes: 35 additions & 0 deletions src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
Expand Up @@ -114,6 +114,8 @@
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;
Expand Down Expand Up @@ -268,6 +270,9 @@ private void handleMessage(@NonNull SignalServiceEnvelope envelope, @NonNull Opt
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...");
Expand Down Expand Up @@ -546,6 +551,36 @@ private void handleSynchronizeStickerPackOperation(@NonNull List<StickerPackOper
}
}

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
Expand Down
106 changes: 106 additions & 0 deletions src/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob.java
@@ -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);
}
}
}
Expand Up @@ -14,6 +14,7 @@
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;
Expand Down Expand Up @@ -117,6 +118,10 @@ public void onRun() throws IOException {
}

database.setProfileAvatar(recipient.getId(), profileAvatar);

if (recipient.isLocalNumber()) {
TextSecurePreferences.setProfileAvatarId(context, Util.getSecureRandom().nextInt());
}
}

@Override
Expand Down

0 comments on commit c702ff6

Please sign in to comment.