Support for syncing contact colors and block lists

Closes #5638
// FREEBIE
This commit is contained in:
Moxie Marlinspike 2016-08-26 16:53:23 -07:00
parent 0a569676f7
commit 32f5bd5336
9 changed files with 194 additions and 7 deletions

View File

@ -72,7 +72,7 @@ dependencies {
compile 'org.whispersystems:jobmanager:1.0.2' compile 'org.whispersystems:jobmanager:1.0.2'
compile 'org.whispersystems:libpastelog:1.0.7' compile 'org.whispersystems:libpastelog:1.0.7'
compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
compile 'org.whispersystems:signal-service-android:2.2.0' compile 'org.whispersystems:signal-service-android:2.3.0'
compile 'com.h6ah4i.android.compat:mulsellistprefcompat:1.0.0' compile 'com.h6ah4i.android.compat:mulsellistprefcompat:1.0.0'
compile 'com.google.zxing:core:3.2.1' compile 'com.google.zxing:core:3.2.1'
@ -130,7 +130,7 @@ dependencyVerification {
'org.whispersystems:jobmanager:506f679fc2fcf7bb6d10f00f41d6f6ea0abf75c70dc95b913398661ad538a181', 'org.whispersystems:jobmanager:506f679fc2fcf7bb6d10f00f41d6f6ea0abf75c70dc95b913398661ad538a181',
'org.whispersystems:libpastelog:bb331d9a98240fc139101128ba836c1edec3c40e000597cdbb29ebf4cbf34d88', 'org.whispersystems:libpastelog:bb331d9a98240fc139101128ba836c1edec3c40e000597cdbb29ebf4cbf34d88',
'com.amulyakhare:com.amulyakhare.textdrawable:54c92b5fba38cfd316a07e5a30528068f45ce8515a6890f1297df4c401af5dcb', 'com.amulyakhare:com.amulyakhare.textdrawable:54c92b5fba38cfd316a07e5a30528068f45ce8515a6890f1297df4c401af5dcb',
'org.whispersystems:signal-service-android:96a926b0bfd1df8b66be2b574e8b8d6ef1862f715b0f1a5457a2038b28d3ad1b', 'org.whispersystems:signal-service-android:2831fdde52117615a7ec479d76ba9cbcdc51d0845db28723981bd0e9426aadb7',
'com.h6ah4i.android.compat:mulsellistprefcompat:47167c5cb796de1a854788e9ff318358e36c8fb88123baaa6e38fb78511dfabe', 'com.h6ah4i.android.compat:mulsellistprefcompat:47167c5cb796de1a854788e9ff318358e36c8fb88123baaa6e38fb78511dfabe',
'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259', 'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259',
'cn.carbswang.android:NumberPickerView:18b3c316d62c7c277978a8d4ed57a5b8f4e943762264960f579a8a549c756729', 'cn.carbswang.android:NumberPickerView:18b3c316d62c7c277978a8d4ed57a5b8f4e943762264960f579a8a549c756729',
@ -140,7 +140,7 @@ dependencyVerification {
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f', 'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f',
'org.whispersystems:signal-protocol-android:d83cb3d15b667fc2543fa18ce80791c72c053e8ac54fc2941f0429a5944ca691', 'org.whispersystems:signal-protocol-android:d83cb3d15b667fc2543fa18ce80791c72c053e8ac54fc2941f0429a5944ca691',
'org.whispersystems:signal-service-java:7932363fec666fdc0b4b424eeca4bdca235f6bf2f226fb6a6ff742c49fc37087', 'org.whispersystems:signal-service-java:f6541d4e4f8bd83f618696db7ff6c78215110ed82de46d19aa541a64ec69226a',
'com.google.android.gms:play-services-basement:e1d29b21e02fd2a63e5a31807415cbb17a59568e27e3254181c01ffae10659bf', 'com.google.android.gms:play-services-basement:e1d29b21e02fd2a63e5a31807415cbb17a59568e27e3254181c01ffae10659bf',
'org.whispersystems:curve25519-android:d6a3ef3a70622af4c728b7fe5f8fdfc9e6cd39b1d39b2c77e7a2add9d876bc23', 'org.whispersystems:curve25519-android:d6a3ef3a70622af4c728b7fe5f8fdfc9e6cd39b1d39b2c77e7a2add9d876bc23',
'org.whispersystems:signal-protocol-java:d518d52eeb3c44210e0b6c687360848a87afbaee0bdf42e2a8dd9974d54fdb3a', 'org.whispersystems:signal-protocol-java:d518d52eeb3c44210e0b6c687360848a87afbaee0bdf42e2a8dd9974d54fdb3a',

View File

@ -95,6 +95,7 @@ import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
import org.thoughtcrime.securesms.database.MmsSmsColumns.Types; import org.thoughtcrime.securesms.database.MmsSmsColumns.Types;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences; import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences;
import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob;
import org.thoughtcrime.securesms.mms.AttachmentManager; import org.thoughtcrime.securesms.mms.AttachmentManager;
import org.thoughtcrime.securesms.mms.AttachmentManager.MediaType; import org.thoughtcrime.securesms.mms.AttachmentManager.MediaType;
@ -566,6 +567,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
protected Void doInBackground(Void... params) { protected Void doInBackground(Void... params) {
DatabaseFactory.getRecipientPreferenceDatabase(ConversationActivity.this) DatabaseFactory.getRecipientPreferenceDatabase(ConversationActivity.this)
.setBlocked(recipients, false); .setBlocked(recipients, false);
ApplicationContext.getInstance(ConversationActivity.this)
.getJobManager()
.add(new MultiDeviceBlockedUpdateJob(ConversationActivity.this));
return null; return null;
} }
}.execute(); }.execute();

View File

@ -33,6 +33,8 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.VibrateState; import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.VibrateState;
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
import org.thoughtcrime.securesms.preferences.AdvancedRingtonePreference; import org.thoughtcrime.securesms.preferences.AdvancedRingtonePreference;
import org.thoughtcrime.securesms.preferences.ColorPreference; import org.thoughtcrime.securesms.preferences.ColorPreference;
import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFactory;
@ -357,8 +359,13 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
new AsyncTask<Void, Void, Void>() { new AsyncTask<Void, Void, Void>() {
@Override @Override
protected Void doInBackground(Void... params) { protected Void doInBackground(Void... params) {
DatabaseFactory.getRecipientPreferenceDatabase(getActivity()) Context context = getActivity();
DatabaseFactory.getRecipientPreferenceDatabase(context)
.setColor(recipients, selectedColor); .setColor(recipients, selectedColor);
ApplicationContext.getInstance(context)
.getJobManager()
.add(new MultiDeviceContactUpdateJob(context, recipients.getPrimaryRecipient().getRecipientId()));
return null; return null;
} }
}.execute(); }.execute();
@ -459,8 +466,14 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
new AsyncTask<Void, Void, Void>() { new AsyncTask<Void, Void, Void>() {
@Override @Override
protected Void doInBackground(Void... params) { protected Void doInBackground(Void... params) {
DatabaseFactory.getRecipientPreferenceDatabase(getActivity()) Context context = getActivity();
DatabaseFactory.getRecipientPreferenceDatabase(context)
.setBlocked(recipients, blocked); .setBlocked(recipients, blocked);
ApplicationContext.getInstance(context)
.getJobManager()
.add(new MultiDeviceBlockedUpdateJob(context));
return null; return null;
} }
}.execute(); }.execute();

View File

@ -66,7 +66,7 @@ public class ContactAccessor {
final String[] inProjection = new String[]{PhoneLookup._ID, PhoneLookup.DISPLAY_NAME}; final String[] inProjection = new String[]{PhoneLookup._ID, PhoneLookup.DISPLAY_NAME};
List<String> pushNumbers = TextSecureDirectory.getInstance(context).getActiveNumbers(); List<String> pushNumbers = TextSecureDirectory.getInstance(context).getActiveNumbers();
final Collection<ContactData> lookupData = new ArrayList<ContactData>(pushNumbers.size()); final Collection<ContactData> lookupData = new ArrayList<>(pushNumbers.size());
for (String pushNumber : pushNumbers) { for (String pushNumber : pushNumbers) {
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(pushNumber)); Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(pushNumber));

View File

@ -11,6 +11,7 @@ import android.support.annotation.Nullable;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.color.MaterialColor; import org.thoughtcrime.securesms.color.MaterialColor;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
@ -80,6 +81,10 @@ public class RecipientPreferenceDatabase extends Database {
return cursor; return cursor;
} }
public BlockedReader readerForBlocked(Cursor cursor) {
return new BlockedReader(context, cursor);
}
public Optional<RecipientsPreferences> getRecipientsPreferences(@NonNull long[] recipients) { public Optional<RecipientsPreferences> getRecipientsPreferences(@NonNull long[] recipients) {
Arrays.sort(recipients); Arrays.sort(recipients);
@ -255,4 +260,28 @@ public class RecipientPreferenceDatabase extends Database {
return expireMessages; return expireMessages;
} }
} }
public static class BlockedReader {
private final Context context;
private final Cursor cursor;
public BlockedReader(Context context, Cursor cursor) {
this.context = context;
this.cursor = cursor;
}
public @NonNull Recipients getCurrent() {
String recipientIds = cursor.getString(cursor.getColumnIndexOrThrow(RECIPIENT_IDS));
return RecipientFactory.getRecipientsForIds(context, recipientIds, false);
}
public @Nullable Recipients getNext() {
if (!cursor.moveToNext()) {
return null;
}
return getCurrent();
}
}
} }

View File

@ -10,6 +10,7 @@ import org.thoughtcrime.securesms.jobs.CleanPreKeysJob;
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob; import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
import org.thoughtcrime.securesms.jobs.DeliveryReceiptJob; import org.thoughtcrime.securesms.jobs.DeliveryReceiptJob;
import org.thoughtcrime.securesms.jobs.GcmRefreshJob; import org.thoughtcrime.securesms.jobs.GcmRefreshJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceGroupUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceGroupUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob;
@ -45,6 +46,7 @@ import dagger.Provides;
MultiDeviceContactUpdateJob.class, MultiDeviceContactUpdateJob.class,
MultiDeviceGroupUpdateJob.class, MultiDeviceGroupUpdateJob.class,
MultiDeviceReadUpdateJob.class, MultiDeviceReadUpdateJob.class,
MultiDeviceBlockedUpdateJob.class,
DeviceListFragment.class, DeviceListFragment.class,
RefreshAttributesJob.class, RefreshAttributesJob.class,
GcmRefreshJob.class}) GcmRefreshJob.class})

View File

@ -0,0 +1,79 @@
package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.BlockedReader;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.dependencies.TextSecureCommunicationModule.TextSecureMessageSenderFactory;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import javax.inject.Inject;
public class MultiDeviceBlockedUpdateJob extends MasterSecretJob implements InjectableType {
private static final long serialVersionUID = 1L;
private static final String TAG = MultiDeviceBlockedUpdateJob.class.getSimpleName();
@Inject transient TextSecureMessageSenderFactory messageSenderFactory;
public MultiDeviceBlockedUpdateJob(Context context) {
super(context, JobParameters.newBuilder()
.withRequirement(new NetworkRequirement(context))
.withRequirement(new MasterSecretRequirement(context))
.withGroupId(MultiDeviceBlockedUpdateJob.class.getSimpleName())
.withPersistence()
.create());
}
@Override
public void onRun(MasterSecret masterSecret)
throws IOException, UntrustedIdentityException
{
RecipientPreferenceDatabase database = DatabaseFactory.getRecipientPreferenceDatabase(context);
SignalServiceMessageSender messageSender = messageSenderFactory.create();
BlockedReader reader = database.readerForBlocked(database.getBlocked());
List<String> blocked = new LinkedList<>();
Recipients recipients;
while ((recipients = reader.getNext()) != null) {
if (recipients.isSingleRecipient()) {
blocked.add(recipients.getPrimaryRecipient().getNumber());
}
}
messageSender.sendMessage(SignalServiceSyncMessage.forBlocked(new BlockedListMessage(blocked)));
}
@Override
public boolean onShouldRetryThrowable(Exception exception) {
if (exception instanceof PushNetworkException) return true;
return false;
}
@Override
public void onAdded() {
}
@Override
public void onCanceled() {
}
}

View File

@ -6,6 +6,7 @@ import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.provider.ContactsContract; import android.provider.ContactsContract;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.contacts.ContactAccessor; import org.thoughtcrime.securesms.contacts.ContactAccessor;
@ -14,6 +15,9 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.dependencies.TextSecureCommunicationModule.TextSecureMessageSenderFactory; import org.thoughtcrime.securesms.dependencies.TextSecureCommunicationModule.TextSecureMessageSenderFactory;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.NetworkRequirement; import org.whispersystems.jobqueue.requirements.NetworkRequirement;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
@ -43,18 +47,56 @@ public class MultiDeviceContactUpdateJob extends MasterSecretJob implements Inje
@Inject transient TextSecureMessageSenderFactory messageSenderFactory; @Inject transient TextSecureMessageSenderFactory messageSenderFactory;
private final long recipientId;
public MultiDeviceContactUpdateJob(Context context) { public MultiDeviceContactUpdateJob(Context context) {
this(context, -1);
}
public MultiDeviceContactUpdateJob(Context context, long recipientId) {
super(context, JobParameters.newBuilder() super(context, JobParameters.newBuilder()
.withRequirement(new NetworkRequirement(context)) .withRequirement(new NetworkRequirement(context))
.withRequirement(new MasterSecretRequirement(context)) .withRequirement(new MasterSecretRequirement(context))
.withGroupId(MultiDeviceContactUpdateJob.class.getSimpleName()) .withGroupId(MultiDeviceContactUpdateJob.class.getSimpleName())
.withPersistence() .withPersistence()
.create()); .create());
this.recipientId = recipientId;
} }
@Override @Override
public void onRun(MasterSecret masterSecret) public void onRun(MasterSecret masterSecret)
throws IOException, UntrustedIdentityException, NetworkException throws IOException, UntrustedIdentityException, NetworkException
{
if (recipientId <= 0) generateFullContactUpdate();
else generateSingleContactUpdate(recipientId);
}
private void generateSingleContactUpdate(long recipientId)
throws IOException, UntrustedIdentityException, NetworkException
{
SignalServiceMessageSender messageSender = messageSenderFactory.create();
File contactDataFile = createTempFile("multidevice-contact-update");
try {
DeviceContactsOutputStream out = new DeviceContactsOutputStream(new FileOutputStream(contactDataFile));
Recipient recipient = RecipientFactory.getRecipientForId(context, recipientId, false);
out.write(new DeviceContact(recipient.getNumber(),
Optional.fromNullable(recipient.getName()),
getAvatar(recipient.getContactUri()),
Optional.fromNullable(recipient.getColor().serialize())));
out.close();
sendUpdate(messageSender, contactDataFile);
} finally {
if (contactDataFile != null) contactDataFile.delete();
}
}
private void generateFullContactUpdate()
throws IOException, UntrustedIdentityException, NetworkException
{ {
SignalServiceMessageSender messageSender = messageSenderFactory.create(); SignalServiceMessageSender messageSender = messageSenderFactory.create();
File contactDataFile = createTempFile("multidevice-contact-update"); File contactDataFile = createTempFile("multidevice-contact-update");
@ -67,8 +109,9 @@ public class MultiDeviceContactUpdateJob extends MasterSecretJob implements Inje
Uri contactUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, String.valueOf(contactData.id)); Uri contactUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, String.valueOf(contactData.id));
String number = contactData.numbers.get(0).number; String number = contactData.numbers.get(0).number;
Optional<String> name = Optional.fromNullable(contactData.name); Optional<String> name = Optional.fromNullable(contactData.name);
Optional<String> color = getColor(number);
out.write(new DeviceContact(number, name, getAvatar(contactUri))); out.write(new DeviceContact(number, name, getAvatar(contactUri), color));
} }
out.close(); out.close();
@ -95,6 +138,15 @@ public class MultiDeviceContactUpdateJob extends MasterSecretJob implements Inje
} }
private Optional<String> getColor(String number) {
if (!TextUtils.isEmpty(number)) {
Recipients recipients = RecipientFactory.getRecipientsFromString(context, number, false);
return Optional.of(recipients.getColor().serialize());
} else {
return Optional.absent();
}
}
private void sendUpdate(SignalServiceMessageSender messageSender, File contactsFile) private void sendUpdate(SignalServiceMessageSender messageSender, File contactsFile)
throws IOException, UntrustedIdentityException, NetworkException throws IOException, UntrustedIdentityException, NetworkException
{ {

View File

@ -286,6 +286,12 @@ public class PushDecryptJob extends ContextJob {
.getJobManager() .getJobManager()
.add(new MultiDeviceGroupUpdateJob(getContext())); .add(new MultiDeviceGroupUpdateJob(getContext()));
} }
if (message.isBlockedListRequest()) {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new MultiDeviceBlockedUpdateJob(getContext()));
}
} }
private void handleSynchronizeReadMessage(@NonNull MasterSecretUnion masterSecret, private void handleSynchronizeReadMessage(@NonNull MasterSecretUnion masterSecret,