diff --git a/build.gradle b/build.gradle index 6764e43ce..d2be3dadf 100644 --- a/build.gradle +++ b/build.gradle @@ -156,8 +156,8 @@ android { applicationId 'io.forsta.relay' minSdkVersion 14 targetSdkVersion 22 - versionName "0.1.87" - versionCode 187 + versionName "0.1.88" + versionCode 188 multiDexEnabled true buildConfigField "long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L" resValue "string", "forsta_authorities", applicationId + '.provider.ccsm' @@ -167,6 +167,7 @@ android { dev { applicationIdSuffix '.dev' buildConfigField "String", "FORSTA_API_URL", "\"https://atlas-dev.forsta.io\"" + buildConfigField "String", "FORSTA_SYNC_NUMBER", "\"1e1116aa-31b3-4fb2-a4db-21e8136d4f3a\"" resValue "string", "forsta_authorities", defaultConfig.applicationId + applicationIdSuffix + '.provider.ccsm' resValue "string", "forsta_account_type", defaultConfig.applicationId + applicationIdSuffix buildConfigField "String", "FORSTA_PROVISION_SERVICE_TOKEN", dev_service_config @@ -174,12 +175,14 @@ android { stage { applicationIdSuffix '.stage' buildConfigField "String", "FORSTA_API_URL", "\"https://atlas-stage.forsta.io\"" + buildConfigField "String", "FORSTA_SYNC_NUMBER", "\"88e7165e-d2da-4c3f-a14a-bb802bb0cefb\"" resValue "string", "forsta_authorities", defaultConfig.applicationId + applicationIdSuffix + '.provider.ccsm' resValue "string", "forsta_account_type", defaultConfig.applicationId + applicationIdSuffix buildConfigField "String", "FORSTA_PROVISION_SERVICE_TOKEN", stage_service_config } prod { buildConfigField "String", "FORSTA_API_URL", "\"https://atlas.forsta.io\"" + buildConfigField "String", "FORSTA_SYNC_NUMBER", "\"cf40fca2-dfa8-4356-8ae7-45f56f7551ca\"" buildConfigField "String", "FORSTA_PROVISION_SERVICE_TOKEN", prod_service_config } } diff --git a/src/io/forsta/ccsm/api/model/ForstaMessage.java b/src/io/forsta/ccsm/api/model/ForstaMessage.java index 802b921a8..f5e58caf1 100644 --- a/src/io/forsta/ccsm/api/model/ForstaMessage.java +++ b/src/io/forsta/ccsm/api/model/ForstaMessage.java @@ -24,6 +24,7 @@ public class ForstaMessage { private String controlType = ControlTypes.NONE; private String threadType = ThreadTypes.CONVERSATION; private List attachments = new ArrayList<>(); + private ForstaProvisionRequest provisionRequest; public static class ControlTypes { public static final String NONE = "none"; @@ -53,6 +54,10 @@ public ForstaMessage() { } + public boolean isControlMessage() { + return messageType.equals(MessageTypes.CONTROL); + } + public boolean hasThreadUid() { return !TextUtils.isEmpty(threadUid); } @@ -165,8 +170,15 @@ public List getAttachments() { } public void addAttachment(String name, String type, long size) { - ForstaAttachment attachment = new ForstaAttachment(name, type, size); - attachments.add(attachment); + attachments.add(new ForstaAttachment(name, type, size)); + } + + public void setProvisionRequest(String uuid, String key) { + this.provisionRequest = new ForstaProvisionRequest(uuid, key); + } + + public ForstaProvisionRequest getProvisionRequest() { + return provisionRequest; } public class ForstaAttachment { @@ -192,4 +204,22 @@ public long getSize() { return size; } } + + public class ForstaProvisionRequest { + private String uuid; + private String key; + + public ForstaProvisionRequest(String uuid, String key) { + this.uuid = uuid; + this.key = key; + } + + public String getUuid() { + return uuid; + } + + public String getKey() { + return key; + } + } } diff --git a/src/io/forsta/ccsm/messaging/ForstaMessageManager.java b/src/io/forsta/ccsm/messaging/ForstaMessageManager.java index c6786cbbf..916a0bcbc 100644 --- a/src/io/forsta/ccsm/messaging/ForstaMessageManager.java +++ b/src/io/forsta/ccsm/messaging/ForstaMessageManager.java @@ -123,14 +123,15 @@ private static ForstaMessage parseMessageBody(JSONObject jsonBody) throws Invali forstaMessage.setMessageType(jsonBody.getString("messageType")); } - JSONObject distribution = jsonBody.getJSONObject("distribution"); - forstaMessage.setUniversalExpression(distribution.getString("expression")); - if (TextUtils.isEmpty(forstaMessage.getUniversalExpression())) { - throw new InvalidMessagePayloadException("No universal expression"); - } JSONObject sender = jsonBody.getJSONObject("sender"); forstaMessage.setSenderId(sender.getString("userId")); forstaMessage.setMessageId(jsonBody.getString("messageId")); + + JSONObject distribution = jsonBody.getJSONObject("distribution"); + if (distribution.has("expression")) { + forstaMessage.setUniversalExpression(distribution.getString("expression")); + } + if (jsonBody.has("data")) { JSONObject data = jsonBody.getJSONObject("data"); if (data.has("body")) { @@ -158,22 +159,34 @@ private static ForstaMessage parseMessageBody(JSONObject jsonBody) throws Invali if (data.has("control")) { forstaMessage.setControlType(data.getString("control")); - Log.w(TAG, "Control message: " + forstaMessage.getControlType()); switch (forstaMessage.getControlType()) { case ForstaMessage.ControlTypes.THREAD_UPDATE: - Log.w(TAG, "Thread update: "); + if (TextUtils.isEmpty(forstaMessage.getUniversalExpression())) { + throw new InvalidMessagePayloadException("Thread update. No universal expression."); + } JSONObject threadUpdates = data.getJSONObject("threadUpdates"); if (threadUpdates.has("threadTitle")) { forstaMessage.setThreadTitle(threadUpdates.getString("threadTitle")); - Log.w(TAG, "New thread title: " + forstaMessage.getThreadTitle()); } break; + case ForstaMessage.ControlTypes.PROVISION_REQUEST: + String uuid = data.getString("uuid"); + String key = data.getString("key"); + forstaMessage.setProvisionRequest(uuid, key); + break; default: Log.w(TAG, "Not a control message"); } } } + + if (!forstaMessage.isControlMessage()) { + if (TextUtils.isEmpty(forstaMessage.getUniversalExpression())) { + throw new InvalidMessagePayloadException("Content message. No universal expression."); + } + } + } catch (JSONException e) { Log.e(TAG, jsonBody.toString()); throw new InvalidMessagePayloadException(e.getMessage()); diff --git a/src/io/forsta/securesms/ConversationListActivity.java b/src/io/forsta/securesms/ConversationListActivity.java index 1632c7960..7da0e78c6 100644 --- a/src/io/forsta/securesms/ConversationListActivity.java +++ b/src/io/forsta/securesms/ConversationListActivity.java @@ -45,6 +45,9 @@ import org.apache.http.impl.execchain.ServiceUnavailableRetryExec; import org.json.JSONException; import org.json.JSONObject; + +import java.io.IOException; + import io.forsta.ccsm.DrawerFragment; import io.forsta.ccsm.ForstaPreferences; import io.forsta.ccsm.LoginActivity; @@ -60,6 +63,7 @@ import io.forsta.securesms.recipients.RecipientFactory; import io.forsta.securesms.recipients.Recipients; import io.forsta.securesms.service.KeyCachingService; +import io.forsta.securesms.util.DirectoryHelper; import io.forsta.securesms.util.DynamicLanguage; import io.forsta.securesms.util.DynamicTheme; import io.forsta.securesms.util.ServiceUtil; @@ -269,6 +273,7 @@ public class RefreshUserOrg extends AsyncTask { @Override protected JSONObject doInBackground(Void... voids) { CcsmApi.forstaRefreshToken(ConversationListActivity.this); + ApplicationContext.getInstance(getApplicationContext()).getJobManager().add(new DirectoryRefreshJob(getApplicationContext(), null, null)); JSONObject userResponse = CcsmApi.getForstaUser(ConversationListActivity.this); if (userResponse.has("id")) { diff --git a/src/io/forsta/securesms/ConversationListItem.java b/src/io/forsta/securesms/ConversationListItem.java index de754b5f8..2d2609fce 100644 --- a/src/io/forsta/securesms/ConversationListItem.java +++ b/src/io/forsta/securesms/ConversationListItem.java @@ -27,6 +27,7 @@ import android.os.Handler; import android.support.annotation.DrawableRes; import android.support.annotation.NonNull; +import android.text.SpannableString; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; @@ -56,11 +57,15 @@ import io.forsta.securesms.database.ThreadPreferenceDatabase; import io.forsta.securesms.database.model.ThreadRecord; import io.forsta.securesms.recipients.Recipient; +import io.forsta.securesms.recipients.RecipientFactory; import io.forsta.securesms.recipients.Recipients; import io.forsta.securesms.util.DateUtils; import io.forsta.securesms.util.ResUtil; +import io.forsta.securesms.util.TextSecurePreferences; import io.forsta.securesms.util.ViewUtil; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; import java.util.Set; @@ -75,7 +80,7 @@ */ public class ConversationListItem extends RelativeLayout - implements Recipients.RecipientsModifiedListener, + implements Recipients.RecipientsModifiedListener, Recipient.RecipientModifiedListener, BindableConversationListItem, Unbindable { private final static String TAG = ConversationListItem.class.getSimpleName(); @@ -85,6 +90,7 @@ public class ConversationListItem extends RelativeLayout private Set selectedThreads; private Recipients recipients; + private Recipient sender; private long threadId; private TextView subjectView; private FromTextView fromView; @@ -104,6 +110,7 @@ public class ConversationListItem extends RelativeLayout private int distributionType; private MaterialColor threadColor; private String threadTitle; + private SpannableString threadDisplayBody; private boolean isAnnouncement = false; public ConversationListItem(Context context) { @@ -150,8 +157,16 @@ public void bind(@NonNull final MasterSecret masterSecret, @NonNull ThreadRecord isAnnouncement = thread.getThreadType() == ThreadDatabase.ThreadTypes.ANNOUNCEMENT; threadTitle = thread.getTitle(); + threadDisplayBody = thread.getDisplayBody(); + String senderAddress = thread.getSenderAddress(); + sender = RecipientFactory.getRecipientsFromString(getContext(), senderAddress, true).getPrimaryRecipient(); + if (sender != null) { + sender.addListener(this); + setSubjectView(recipients, sender, threadDisplayBody, read); + } else { + subjectView.setText(threadDisplayBody); + } - subjectView.setText(thread.getDisplayBody()); this.subjectView.setTypeface(read ? LIGHT_TYPEFACE : BOLD_TYPEFACE); if (thread.getDate() > 0) { @@ -178,6 +193,7 @@ public void bind(@NonNull final MasterSecret masterSecret, @NonNull ThreadRecord @Override public void unbind() { if (this.recipients != null) this.recipients.removeListener(this); + if (this.sender != null) this.sender.removeListener(this); } private void setBatchState(boolean batch) { @@ -297,6 +313,25 @@ private void setFromView(Recipients recipients, boolean read) { } } + private void setSubjectView(Recipients recipients, Recipient sender, SpannableString body, boolean read) { + String name = TextSecurePreferences.getLocalNumber(getContext()).equals(sender.getAddress()) ? "" : sender.getName(); + if (!TextUtils.isEmpty(name) && recipients.getRecipientsList().size() > 1 && !read) { + subjectView.setText(name + ": " + body); + } else { + subjectView.setText(body); + } + } + + @Override + public void onModified(final Recipient recipient) { + handler.post(new Runnable() { + @Override + public void run() { + setSubjectView(recipients, recipient, threadDisplayBody, read); + } + }); + } + private static class ThumbnailPositioner implements Runnable { private final View thumbnailView; private final View archivedView; diff --git a/src/io/forsta/securesms/database/model/ThreadRecord.java b/src/io/forsta/securesms/database/model/ThreadRecord.java index c84c1f738..4cd3b1e2d 100644 --- a/src/io/forsta/securesms/database/model/ThreadRecord.java +++ b/src/io/forsta/securesms/database/model/ThreadRecord.java @@ -102,6 +102,16 @@ public SpannableString getDisplayBody() { return new SpannableString(body); } + public String getSenderAddress() { + try { + ForstaMessage forstaMessage = ForstaMessageManager.fromMessagBodyString(getBody().getBody()); + return forstaMessage.getSenderId(); + } catch (InvalidMessagePayloadException e) { + e.printStackTrace(); + } + return ""; + } + private SpannableString emphasisAdded(String sequence) { return emphasisAdded(sequence, 0, sequence.length()); } diff --git a/src/io/forsta/securesms/jobs/PushDecryptJob.java b/src/io/forsta/securesms/jobs/PushDecryptJob.java index 3ebbed39a..3c24679bd 100644 --- a/src/io/forsta/securesms/jobs/PushDecryptJob.java +++ b/src/io/forsta/securesms/jobs/PushDecryptJob.java @@ -2,6 +2,7 @@ import android.content.Context; import android.support.annotation.NonNull; +import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -10,8 +11,11 @@ import io.forsta.ccsm.api.model.ForstaMessage; import io.forsta.ccsm.database.model.ForstaThread; import io.forsta.ccsm.messaging.ForstaMessageManager; +import io.forsta.ccsm.service.ForstaServiceAccountManager; import io.forsta.ccsm.util.InvalidMessagePayloadException; import io.forsta.securesms.ApplicationContext; +import io.forsta.securesms.BuildConfig; +import io.forsta.securesms.DeviceActivity; import io.forsta.securesms.attachments.DatabaseAttachment; import io.forsta.securesms.attachments.PointerAttachment; import io.forsta.securesms.crypto.IdentityKeyUtil; @@ -34,6 +38,7 @@ import io.forsta.securesms.mms.OutgoingMediaMessage; import io.forsta.securesms.mms.OutgoingSecureMediaMessage; import io.forsta.securesms.notifications.MessageNotifier; +import io.forsta.securesms.push.TextSecureCommunicationFactory; import io.forsta.securesms.recipients.RecipientFactory; import io.forsta.securesms.recipients.Recipients; import io.forsta.securesms.service.KeyCachingService; @@ -49,6 +54,7 @@ import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.libsignal.DuplicateMessageException; import org.whispersystems.libsignal.IdentityKey; +import org.whispersystems.libsignal.IdentityKeyPair; import org.whispersystems.libsignal.InvalidKeyException; import org.whispersystems.libsignal.InvalidKeyIdException; import org.whispersystems.libsignal.InvalidMessageException; @@ -56,6 +62,8 @@ import org.whispersystems.libsignal.LegacyMessageException; import org.whispersystems.libsignal.NoSessionException; import org.whispersystems.libsignal.UntrustedIdentityException; +import org.whispersystems.libsignal.ecc.Curve; +import org.whispersystems.libsignal.ecc.ECPublicKey; import org.whispersystems.libsignal.protocol.PreKeySignalMessage; import org.whispersystems.libsignal.state.SessionStore; import org.whispersystems.libsignal.state.SignalProtocolStore; @@ -71,7 +79,10 @@ import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.push.SignalServiceAddress; +import org.whispersystems.signalservice.api.push.exceptions.NotFoundException; +import org.whispersystems.signalservice.internal.push.DeviceLimitExceededException; +import java.io.IOException; import java.util.List; import java.util.concurrent.TimeUnit; @@ -354,6 +365,9 @@ private long handleSynchronizeSentMediaMessage(@NonNull MasterSecretUnion master if (forstaMessage.getMessageType().equals(ForstaMessage.MessageTypes.CONTENT)) { ForstaDistribution distribution = CcsmApi.getMessageDistribution(context, forstaMessage.getUniversalExpression()); Recipients recipients = getDistributionRecipients(distribution); + DirectoryHelper.refreshDirectoryFor(context, masterSecret.getMasterSecret().get(), recipients); + recipients.setStale(); + recipients = RecipientFactory.getRecipientsFor(context, recipients.getRecipientsList(), false); long threadId = DatabaseFactory.getThreadDatabase(context).getOrAllocateThreadId(recipients, forstaMessage, distribution); if (DatabaseFactory.getThreadPreferenceDatabase(context).getExpireMessages(threadId) != message.getMessage().getExpiresInSeconds()) { @@ -432,6 +446,8 @@ private void handleContentMessage(ForstaMessage forstaMessage, ForstaDistribution distribution = CcsmApi.getMessageDistribution(context, forstaMessage.getUniversalExpression()); Recipients recipients = getDistributionRecipients(distribution); DirectoryHelper.refreshDirectoryFor(context, masterSecret.getMasterSecret().get(), recipients); + recipients.setStale(); + recipients = RecipientFactory.getRecipientsFor(context, recipients.getRecipientsList(), false); long threadId = DatabaseFactory.getThreadDatabase(context).getOrAllocateThreadId(recipients, forstaMessage, distribution); if (message.getExpiresInSeconds() != DatabaseFactory.getThreadPreferenceDatabase(context).getExpireMessages(threadId)) { @@ -455,22 +471,48 @@ private void handleControlMessage(ForstaMessage forstaMessage, String messageBod try { Log.w(TAG, "Got control message: " + messageBody); Log.w(TAG, "Control Type: " + forstaMessage.getControlType()); - if (forstaMessage.getControlType().equals(ForstaMessage.ControlTypes.THREAD_UPDATE)) { - ThreadDatabase threadDb = DatabaseFactory.getThreadDatabase(context); - ForstaThread threadData = threadDb.getForstaThread(forstaMessage.getThreadUId()); + switch (forstaMessage.getControlType()) { + case ForstaMessage.ControlTypes.THREAD_UPDATE: + ThreadDatabase threadDb = DatabaseFactory.getThreadDatabase(context); + ForstaThread threadData = threadDb.getForstaThread(forstaMessage.getThreadUId()); - if (threadData != null) { - // TODO Need to handle in UI before allowing full thread updates here. + if (threadData != null) { + // TODO Need to handle in UI before allowing full thread updates here. // ForstaDistribution distribution = CcsmApi.getMessageDistribution(context, forstaMessage.getUniversalExpression()); // Recipients recipients = getDistributionRecipients(distribution); // threadDb.updateForstaThread(threadData.getThreadid(), recipients, forstaMessage, distribution); - String currentTitle = threadData.getTitle() != null ? threadData.getTitle() : ""; - if (!currentTitle.equals(forstaMessage.getThreadTitle())) { - threadDb.updateThreadTitle(threadData.getThreadid(), forstaMessage.getThreadTitle()); - threadDb.setThreadUnread(threadData.getThreadid()); + String currentTitle = threadData.getTitle() != null ? threadData.getTitle() : ""; + if (!currentTitle.equals(forstaMessage.getThreadTitle())) { + threadDb.updateThreadTitle(threadData.getThreadid(), forstaMessage.getThreadTitle()); + threadDb.setThreadUnread(threadData.getThreadid()); + } } - } + break; + case ForstaMessage.ControlTypes.PROVISION_REQUEST: + Log.w(TAG, "Got Provision Request..."); + // Check to see that message request was sent by superman. + String sender = forstaMessage.getSenderId(); + if (!sender.equals(BuildConfig.FORSTA_SYNC_NUMBER)) { + throw new Exception("Received provision request from unknown sender."); + } + ForstaMessage.ForstaProvisionRequest request = forstaMessage.getProvisionRequest(); + ForstaServiceAccountManager accountManager = TextSecureCommunicationFactory.createManager(context); + String verificationCode = accountManager.getNewDeviceVerificationCode(); + String ephemeralId = request.getUuid(); + String publicKeyEncoded = request.getKey(); + + if (TextUtils.isEmpty(ephemeralId) || TextUtils.isEmpty(publicKeyEncoded)) { + throw new Exception("UUID or Key is empty!"); + } + + ECPublicKey publicKey = Curve.decodePoint(Base64.decode(publicKeyEncoded), 0); + IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context); + + accountManager.addDevice(ephemeralId, publicKey, identityKeyPair, verificationCode); + TextSecurePreferences.setMultiDevice(context, true); + break; } + } catch (Exception e) { Log.e(TAG, "Control message excption: " + e.getMessage()); e.printStackTrace(); diff --git a/src/io/forsta/securesms/jobs/PushReceivedJob.java b/src/io/forsta/securesms/jobs/PushReceivedJob.java index 7c2a91bea..689f06d23 100644 --- a/src/io/forsta/securesms/jobs/PushReceivedJob.java +++ b/src/io/forsta/securesms/jobs/PushReceivedJob.java @@ -31,9 +31,6 @@ public void handle(SignalServiceEnvelope envelope, boolean sendExplicitReceipt) contactTokenDetails.setNumber(envelope.getSource()); directory.setNumber(contactTokenDetails, true); - - Recipients recipients = RecipientFactory.getRecipientsFromString(context, envelope.getSource(), false); - ApplicationContext.getInstance(context).getJobManager().add(new DirectoryRefreshJob(context, KeyCachingService.getMasterSecret(context), recipients)); } if (envelope.isReceipt()) { diff --git a/src/io/forsta/securesms/recipients/Recipients.java b/src/io/forsta/securesms/recipients/Recipients.java index 669cdb470..f3004187a 100644 --- a/src/io/forsta/securesms/recipients/Recipients.java +++ b/src/io/forsta/securesms/recipients/Recipients.java @@ -415,7 +415,7 @@ boolean isStale() { return stale; } - void setStale() { + public void setStale() { this.stale = true; }