package org.thoughtcrime.securesms.jobs; import android.support.annotation.NonNull; 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.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus; import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.InvalidKeyException; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import java.io.IOException; import java.util.concurrent.TimeUnit; import javax.inject.Inject; public class MultiDeviceVerifiedUpdateJob extends BaseJob implements InjectableType { public static final String KEY = "MultiDeviceVerifiedUpdateJob"; private static final String TAG = MultiDeviceVerifiedUpdateJob.class.getSimpleName(); private static final String KEY_DESTINATION = "destination"; private static final String KEY_IDENTITY_KEY = "identity_key"; private static final String KEY_VERIFIED_STATUS = "verified_status"; private static final String KEY_TIMESTAMP = "timestamp"; @Inject SignalServiceMessageSender messageSender; private String destination; private byte[] identityKey; private VerifiedStatus verifiedStatus; private long timestamp; public MultiDeviceVerifiedUpdateJob(Address destination, IdentityKey identityKey, VerifiedStatus verifiedStatus) { this(new Job.Parameters.Builder() .addConstraint(NetworkConstraint.KEY) .setQueue("__MULTI_DEVICE_VERIFIED_UPDATE__") .setLifespan(TimeUnit.DAYS.toMillis(1)) .setMaxAttempts(Parameters.UNLIMITED) .build(), destination, identityKey.serialize(), verifiedStatus, System.currentTimeMillis()); } private MultiDeviceVerifiedUpdateJob(@NonNull Job.Parameters parameters, @NonNull Address destination, @NonNull byte[] identityKey, @NonNull VerifiedStatus verifiedStatus, long timestamp) { super(parameters); this.destination = destination.serialize(); this.identityKey = identityKey; this.verifiedStatus = verifiedStatus; this.timestamp = timestamp; } @Override public @NonNull Data serialize() { return new Data.Builder().putString(KEY_DESTINATION, destination) .putString(KEY_IDENTITY_KEY, Base64.encodeBytes(identityKey)) .putInt(KEY_VERIFIED_STATUS, verifiedStatus.toInt()) .putLong(KEY_TIMESTAMP, timestamp) .build(); } @Override public @NonNull String getFactoryKey() { return KEY; } @Override public void onRun() throws IOException, UntrustedIdentityException { try { if (!TextSecurePreferences.isMultiDevice(context)) { Log.i(TAG, "Not multi device..."); return; } if (destination == null) { Log.w(TAG, "No destination..."); return; } Address canonicalDestination = Address.fromSerialized(destination); VerifiedMessage.VerifiedState verifiedState = getVerifiedState(verifiedStatus); VerifiedMessage verifiedMessage = new VerifiedMessage(canonicalDestination.toPhoneString(), new IdentityKey(identityKey, 0), verifiedState, timestamp); // TODO: Message ID messageSender.sendMessage(0, SignalServiceSyncMessage.forVerified(verifiedMessage), UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(destination), false))); } catch (InvalidKeyException e) { throw new IOException(e); } } private VerifiedMessage.VerifiedState getVerifiedState(VerifiedStatus status) { VerifiedMessage.VerifiedState verifiedState; switch (status) { case DEFAULT: verifiedState = VerifiedMessage.VerifiedState.DEFAULT; break; case VERIFIED: verifiedState = VerifiedMessage.VerifiedState.VERIFIED; break; case UNVERIFIED: verifiedState = VerifiedMessage.VerifiedState.UNVERIFIED; break; default: throw new AssertionError("Unknown status: " + verifiedStatus); } return verifiedState; } @Override public boolean onShouldRetry(@NonNull Exception exception) { return exception instanceof PushNetworkException; } @Override public void onCanceled() { } public static final class Factory implements Job.Factory { @Override public @NonNull MultiDeviceVerifiedUpdateJob create(@NonNull Parameters parameters, @NonNull Data data) { try { Address destination = Address.fromSerialized(data.getString(KEY_DESTINATION)); VerifiedStatus verifiedStatus = VerifiedStatus.forState(data.getInt(KEY_VERIFIED_STATUS)); long timestamp = data.getLong(KEY_TIMESTAMP); byte[] identityKey = Base64.decode(data.getString(KEY_IDENTITY_KEY)); return new MultiDeviceVerifiedUpdateJob(parameters, destination, identityKey, verifiedStatus, timestamp); } catch (IOException e) { throw new AssertionError(e); } } } }