package org.thoughtcrime.securesms.jobs; import android.content.Context; import android.support.annotation.Nullable; import android.util.Log; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.requirements.NetworkRequirement; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream; import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroup; import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsOutputStream; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import javax.inject.Inject; public class MultiDeviceGroupUpdateJob extends MasterSecretJob implements InjectableType { private static final long serialVersionUID = 1L; private static final String TAG = MultiDeviceGroupUpdateJob.class.getSimpleName(); @Inject transient SignalCommunicationModule.SignalMessageSenderFactory messageSenderFactory; public MultiDeviceGroupUpdateJob(Context context) { super(context, JobParameters.newBuilder() .withRequirement(new NetworkRequirement(context)) .withRequirement(new MasterSecretRequirement(context)) .withGroupId(MultiDeviceGroupUpdateJob.class.getSimpleName()) .withPersistence() .create()); } @Override public void onRun(MasterSecret masterSecret) throws Exception { SignalServiceMessageSender messageSender = messageSenderFactory.create(); File contactDataFile = createTempFile("multidevice-contact-update"); GroupDatabase.Reader reader = null; GroupDatabase.GroupRecord record; try { DeviceGroupsOutputStream out = new DeviceGroupsOutputStream(new FileOutputStream(contactDataFile)); reader = DatabaseFactory.getGroupDatabase(context).getGroups(); while ((record = reader.getNext()) != null) { out.write(new DeviceGroup(record.getId(), Optional.fromNullable(record.getTitle()), record.getMembers(), getAvatar(record.getAvatar()), record.isActive())); } out.close(); if (contactDataFile.exists() && contactDataFile.length() > 0) { sendUpdate(messageSender, contactDataFile); } else { Log.w(TAG, "No groups present for sync message..."); } } finally { if (contactDataFile != null) contactDataFile.delete(); if (reader != null) reader.close(); } } @Override public boolean onShouldRetryThrowable(Exception exception) { if (exception instanceof PushNetworkException) return true; return false; } @Override public void onAdded() { } @Override public void onCanceled() { } private void sendUpdate(SignalServiceMessageSender messageSender, File contactsFile) throws IOException, UntrustedIdentityException { FileInputStream contactsFileStream = new FileInputStream(contactsFile); SignalServiceAttachmentStream attachmentStream = SignalServiceAttachment.newStreamBuilder() .withStream(contactsFileStream) .withContentType("application/octet-stream") .withLength(contactsFile.length()) .build(); messageSender.sendMessage(SignalServiceSyncMessage.forGroups(attachmentStream)); } private Optional<SignalServiceAttachmentStream> getAvatar(@Nullable byte[] avatar) { if (avatar == null) return Optional.absent(); return Optional.of(SignalServiceAttachment.newStreamBuilder() .withStream(new ByteArrayInputStream(avatar)) .withContentType("image/*") .withLength(avatar.length) .build()); } private File createTempFile(String prefix) throws IOException { File file = File.createTempFile(prefix, "tmp", context.getCacheDir()); file.deleteOnExit(); return file; } }