From 0b20e99cd261e64fb49cf58b6beeea5616ea4e53 Mon Sep 17 00:00:00 2001
From: Moxie Marlinspike <moxie@thoughtcrime.org>
Date: Wed, 30 Sep 2015 16:19:50 -0700
Subject: [PATCH] Refresh attributes to signal voice support on update.

// FREEBIE
---
 build.gradle                                  |  7 ++--
 .../signaling/RedPhoneAccountManager.java     |  4 +++
 .../signaling/UnauthorizedException.java      |  9 ++++++
 .../securesms/ApplicationContext.java         |  3 ++
 .../securesms/DatabaseUpgradeActivity.java    |  9 ++++++
 .../RedPhoneCommunicationModule.java          | 32 +++++++++++++++++++
 .../TextSecureCommunicationModule.java        |  4 ++-
 .../securesms/jobs/GcmRefreshJob.java         | 23 ++++++++++---
 .../securesms/jobs/RefreshAttributesJob.java  | 20 +++++++++---
 9 files changed, 95 insertions(+), 16 deletions(-)
 create mode 100644 src/org/thoughtcrime/redphone/signaling/UnauthorizedException.java
 create mode 100644 src/org/thoughtcrime/securesms/dependencies/RedPhoneCommunicationModule.java

diff --git a/build.gradle b/build.gradle
index e3ffcf0595..4c709a3301 100644
--- a/build.gradle
+++ b/build.gradle
@@ -75,10 +75,10 @@ dependencies {
     compile 'com.commonsware.cwac:camera:0.6.12'
     provided 'com.squareup.dagger:dagger-compiler:1.2.2'
 
-    compile 'org.whispersystems:jobmanager:0.11.0'
+    compile 'org.whispersystems:jobmanager:1.0.2'
     compile 'org.whispersystems:libpastelog:1.0.6'
     compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
-    compile 'org.whispersystems:textsecure-android:1.8.0'
+    compile 'org.whispersystems:textsecure-android:1.8.1'
     compile 'com.h6ah4i.android.compat:mulsellistprefcompat:1.0.0'
 
     testCompile 'junit:junit:4.12'
@@ -126,15 +126,12 @@ dependencyVerification {
             'com.doomonafireball.betterpickers:library:132ecd685c95a99e7377c4e27bfadbb2d7ed0bea995944060cd62d4369fdaf3d',
             'com.madgag.spongycastle:prov:b8c3fec3a59aac1aa04ccf4dad7179351e54ef7672f53f508151b614c131398a',
             'com.commonsware.cwac:camera:dcc93ddbb2f0393114fa1f31a13fe9e6edfcf5dbe96b22bc4b66c7b15e179054',
-            'org.whispersystems:jobmanager:ea9cb943c4892fb90c1eea1be30efeb85cefca213d52c788419553b58d0ed70d',
             'org.whispersystems:libpastelog:550d33c565380d90f4c671e7b8ed5f3a6da55a9fda468373177106b2eb5220b2',
             'com.amulyakhare:com.amulyakhare.textdrawable:54c92b5fba38cfd316a07e5a30528068f45ce8515a6890f1297df4c401af5dcb',
-            'org.whispersystems:textsecure-android:226214454003de852d856509ce9ca90a0865f7ad85a45f7a769916c6362fd4d7',
             'com.h6ah4i.android.compat:mulsellistprefcompat:47167c5cb796de1a854788e9ff318358e36c8fb88123baaa6e38fb78511dfabe',
             'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a',
             'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
             'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f',
-            'org.whispersystems:textsecure-java:97ef37e2ad88c6d7a896680bbab051ea894dc45409bc9b4683f75b55718fbeb5',
             'org.whispersystems:axolotl-android:40d3db5004a84749a73f68d2f0d01b2ae35a73c54df96d8c6c6723b96efb6fc0',
             'com.googlecode.libphonenumber:libphonenumber:eba17eae81dd622ea89a00a3a8c025b2f25d342e0d9644c5b62e16f15687c3ab',
             'com.google.protobuf:protobuf-java:e0c1c64575c005601725e7c6a02cebf9e1285e888f756b2a1d73ffa8d725cc74',
diff --git a/src/org/thoughtcrime/redphone/signaling/RedPhoneAccountManager.java b/src/org/thoughtcrime/redphone/signaling/RedPhoneAccountManager.java
index d10215bcba..121f1232e9 100644
--- a/src/org/thoughtcrime/redphone/signaling/RedPhoneAccountManager.java
+++ b/src/org/thoughtcrime/redphone/signaling/RedPhoneAccountManager.java
@@ -60,6 +60,10 @@ public class RedPhoneAccountManager {
 
     Response response = client.newCall(builder.build()).execute();
 
+    if (response.code() == 401 || response.code() == 403) {
+      throw new UnauthorizedException("Failed to perform GCM operation: " + response.code());
+    }
+
     if (!response.isSuccessful()) {
       throw new IOException("Failed to perform GCM operation: " + response.code());
     }
diff --git a/src/org/thoughtcrime/redphone/signaling/UnauthorizedException.java b/src/org/thoughtcrime/redphone/signaling/UnauthorizedException.java
new file mode 100644
index 0000000000..ff4d3a0d30
--- /dev/null
+++ b/src/org/thoughtcrime/redphone/signaling/UnauthorizedException.java
@@ -0,0 +1,9 @@
+package org.thoughtcrime.redphone.signaling;
+
+import java.io.IOException;
+
+public class UnauthorizedException extends IOException {
+  public UnauthorizedException(String s) {
+    super(s);
+  }
+}
diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java
index 2eeb5bd210..aaab48328d 100644
--- a/src/org/thoughtcrime/securesms/ApplicationContext.java
+++ b/src/org/thoughtcrime/securesms/ApplicationContext.java
@@ -25,8 +25,10 @@ import android.os.StrictMode.VmPolicy;
 import org.thoughtcrime.securesms.crypto.PRNGFixes;
 import org.thoughtcrime.securesms.dependencies.AxolotlStorageModule;
 import org.thoughtcrime.securesms.dependencies.InjectableType;
+import org.thoughtcrime.securesms.dependencies.RedPhoneCommunicationModule;
 import org.thoughtcrime.securesms.dependencies.TextSecureCommunicationModule;
 import org.thoughtcrime.securesms.jobs.GcmRefreshJob;
+import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
 import org.thoughtcrime.securesms.jobs.persistence.EncryptingJobSerializer;
 import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirementProvider;
 import org.thoughtcrime.securesms.jobs.requirements.MediaNetworkRequirementProvider;
@@ -118,6 +120,7 @@ public class ApplicationContext extends Application implements DependencyInjecto
 
   private void initializeDependencyInjection() {
     this.objectGraph = ObjectGraph.create(new TextSecureCommunicationModule(this),
+                                          new RedPhoneCommunicationModule(this),
                                           new AxolotlStorageModule(this));
   }
 
diff --git a/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java b/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java
index 40733130ff..6570c6715d 100644
--- a/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java
+++ b/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java
@@ -41,6 +41,7 @@ import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
 import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
 import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
 import org.thoughtcrime.securesms.jobs.PushDecryptJob;
+import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
 import org.thoughtcrime.securesms.notifications.MessageNotifier;
 import org.thoughtcrime.securesms.util.Util;
 import org.thoughtcrime.securesms.util.VersionTracker;
@@ -67,6 +68,7 @@ public class DatabaseUpgradeActivity extends BaseActivity {
   public static final int MIGRATE_SESSION_PLAINTEXT            = 136;
   public static final int CONTACTS_ACCOUNT_VERSION             = 136;
   public static final int MEDIA_DOWNLOAD_CONTROLS_VERSION      = 151;
+  public static final int REDPHONE_SUPPORT_VERSION             = 157;
 
   private static final SortedSet<Integer> UPGRADE_VERSIONS = new TreeSet<Integer>() {{
     add(NO_MORE_KEY_EXCHANGE_PREFIX_VERSION);
@@ -79,6 +81,7 @@ public class DatabaseUpgradeActivity extends BaseActivity {
     add(PUSH_DECRYPT_SERIAL_ID_VERSION);
     add(MIGRATE_SESSION_PLAINTEXT);
     add(MEDIA_DOWNLOAD_CONTROLS_VERSION);
+    add(REDPHONE_SUPPORT_VERSION);
   }};
 
   private MasterSecret masterSecret;
@@ -220,6 +223,12 @@ public class DatabaseUpgradeActivity extends BaseActivity {
         schedulePendingIncomingParts(context);
       }
 
+      if (params[0] < REDPHONE_SUPPORT_VERSION) {
+        ApplicationContext.getInstance(getApplicationContext())
+                          .getJobManager()
+                          .add(new RefreshAttributesJob(getApplicationContext()));
+      }
+
       return null;
     }
 
diff --git a/src/org/thoughtcrime/securesms/dependencies/RedPhoneCommunicationModule.java b/src/org/thoughtcrime/securesms/dependencies/RedPhoneCommunicationModule.java
new file mode 100644
index 0000000000..584f37a7f6
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/dependencies/RedPhoneCommunicationModule.java
@@ -0,0 +1,32 @@
+package org.thoughtcrime.securesms.dependencies;
+
+import android.content.Context;
+
+import org.thoughtcrime.redphone.signaling.RedPhoneAccountManager;
+import org.thoughtcrime.redphone.signaling.RedPhoneTrustStore;
+import org.thoughtcrime.securesms.BuildConfig;
+import org.thoughtcrime.securesms.jobs.GcmRefreshJob;
+import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
+import org.thoughtcrime.securesms.util.TextSecurePreferences;
+
+import dagger.Module;
+import dagger.Provides;
+
+@Module(complete = false, injects = {GcmRefreshJob.class,
+                                     RefreshAttributesJob.class})
+public class RedPhoneCommunicationModule {
+
+  private final Context context;
+
+  public RedPhoneCommunicationModule(Context context) {
+    this.context = context;
+  }
+
+  @Provides RedPhoneAccountManager provideRedPhoneAccountManager() {
+    return new RedPhoneAccountManager(BuildConfig.REDPHONE_MASTER_URL,
+                                      new RedPhoneTrustStore(context),
+                                      TextSecurePreferences.getLocalNumber(context),
+                                      TextSecurePreferences.getPushServerPassword(context));
+  }
+
+}
diff --git a/src/org/thoughtcrime/securesms/dependencies/TextSecureCommunicationModule.java b/src/org/thoughtcrime/securesms/dependencies/TextSecureCommunicationModule.java
index 3c6ece846b..0d999b5728 100644
--- a/src/org/thoughtcrime/securesms/dependencies/TextSecureCommunicationModule.java
+++ b/src/org/thoughtcrime/securesms/dependencies/TextSecureCommunicationModule.java
@@ -9,6 +9,7 @@ import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
 import org.thoughtcrime.securesms.jobs.CleanPreKeysJob;
 import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
 import org.thoughtcrime.securesms.jobs.DeliveryReceiptJob;
+import org.thoughtcrime.securesms.jobs.GcmRefreshJob;
 import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
 import org.thoughtcrime.securesms.jobs.MultiDeviceGroupUpdateJob;
 import org.thoughtcrime.securesms.jobs.PushGroupSendJob;
@@ -43,7 +44,8 @@ import dagger.Provides;
                                      MultiDeviceContactUpdateJob.class,
                                      MultiDeviceGroupUpdateJob.class,
                                      DeviceListActivity.DeviceListFragment.class,
-                                     RefreshAttributesJob.class})
+                                     RefreshAttributesJob.class,
+                                     GcmRefreshJob.class})
 public class TextSecureCommunicationModule {
 
   private final Context context;
diff --git a/src/org/thoughtcrime/securesms/jobs/GcmRefreshJob.java b/src/org/thoughtcrime/securesms/jobs/GcmRefreshJob.java
index 74e99ecfae..c704864bbb 100644
--- a/src/org/thoughtcrime/securesms/jobs/GcmRefreshJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/GcmRefreshJob.java
@@ -28,9 +28,11 @@ import com.google.android.gms.common.ConnectionResult;
 import com.google.android.gms.common.GooglePlayServicesUtil;
 import com.google.android.gms.gcm.GoogleCloudMessaging;
 
+import org.thoughtcrime.redphone.signaling.RedPhoneAccountManager;
+import org.thoughtcrime.redphone.signaling.UnauthorizedException;
 import org.thoughtcrime.securesms.PlayServicesProblemActivity;
 import org.thoughtcrime.securesms.R;
-import org.thoughtcrime.securesms.push.TextSecureCommunicationFactory;
+import org.thoughtcrime.securesms.dependencies.InjectableType;
 import org.thoughtcrime.securesms.util.TextSecurePreferences;
 import org.whispersystems.jobqueue.JobParameters;
 import org.whispersystems.jobqueue.requirements.NetworkRequirement;
@@ -38,12 +40,17 @@ import org.whispersystems.libaxolotl.util.guava.Optional;
 import org.whispersystems.textsecure.api.TextSecureAccountManager;
 import org.whispersystems.textsecure.api.push.exceptions.NonSuccessfulResponseCodeException;
 
-public class GcmRefreshJob extends ContextJob {
+import javax.inject.Inject;
+
+public class GcmRefreshJob extends ContextJob implements InjectableType {
 
   private static final String TAG = GcmRefreshJob.class.getSimpleName();
 
   public static final String REGISTRATION_ID = "312334754206";
 
+  @Inject transient TextSecureAccountManager textSecureAccountManager;
+  @Inject transient RedPhoneAccountManager   redPhoneAccountManager;
+
   public GcmRefreshJob(Context context) {
     super(context, JobParameters.newBuilder().withRequirement(new NetworkRequirement(context)).create());
   }
@@ -53,8 +60,7 @@ public class GcmRefreshJob extends ContextJob {
 
   @Override
   public void onRun() throws Exception {
-    TextSecureAccountManager accountManager = TextSecureCommunicationFactory.createManager(context);
-    String                   registrationId = TextSecurePreferences.getGcmRegistrationId(context);
+    String registrationId = TextSecurePreferences.getGcmRegistrationId(context);
 
     if (registrationId == null) {
       Log.w(TAG, "GCM registrationId expired, reregistering...");
@@ -64,7 +70,14 @@ public class GcmRefreshJob extends ContextJob {
         notifyGcmFailure();
       } else {
         String gcmId = GoogleCloudMessaging.getInstance(context).register(REGISTRATION_ID);
-        accountManager.setGcmId(Optional.of(gcmId));
+        textSecureAccountManager.setGcmId(Optional.of(gcmId));
+
+        try {
+          redPhoneAccountManager.setGcmId(Optional.of(gcmId));
+        } catch (UnauthorizedException e) {
+          Log.w(TAG, e);
+        }
+
         TextSecurePreferences.setGcmRegistrationId(context, gcmId);
         TextSecurePreferences.setWebsocketRegistered(context, true);
       }
diff --git a/src/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java b/src/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java
index f50f5068a6..654b3ce304 100644
--- a/src/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java
@@ -3,6 +3,9 @@ package org.thoughtcrime.securesms.jobs;
 import android.content.Context;
 import android.util.Log;
 
+import org.thoughtcrime.redphone.signaling.RedPhoneAccountAttributes;
+import org.thoughtcrime.redphone.signaling.RedPhoneAccountManager;
+import org.thoughtcrime.securesms.dependencies.InjectableType;
 import org.thoughtcrime.securesms.util.TextSecurePreferences;
 import org.whispersystems.jobqueue.JobParameters;
 import org.whispersystems.jobqueue.requirements.NetworkRequirement;
@@ -13,11 +16,14 @@ import java.io.IOException;
 
 import javax.inject.Inject;
 
-public class RefreshAttributesJob extends ContextJob {
+public class RefreshAttributesJob extends ContextJob implements InjectableType {
+
+  public static final long serialVersionUID = 1L;
 
   private static final String TAG = RefreshAttributesJob.class.getSimpleName();
 
-  @Inject TextSecureAccountManager accountManager;
+  @Inject transient TextSecureAccountManager textSecureAccountManager;
+  @Inject transient RedPhoneAccountManager   redPhoneAccountManager;
 
   public RefreshAttributesJob(Context context) {
     super(context, JobParameters.newBuilder()
@@ -32,10 +38,14 @@ public class RefreshAttributesJob extends ContextJob {
 
   @Override
   public void onRun() throws IOException {
-    String signalingKey   = TextSecurePreferences.getSignalingKey(context);
-    int    registrationId = TextSecurePreferences.getLocalRegistrationId(context);
+    String signalingKey      = TextSecurePreferences.getSignalingKey(context);
+    String gcmRegistrationId = TextSecurePreferences.getGcmRegistrationId(context);
+    int    registrationId    = TextSecurePreferences.getLocalRegistrationId(context);
+
+    String token = textSecureAccountManager.getAccountVerificationToken();
 
-    accountManager.setAccountAttributes(signalingKey, registrationId, true);
+    redPhoneAccountManager.createAccount(token, new RedPhoneAccountAttributes(signalingKey, gcmRegistrationId));
+    textSecureAccountManager.setAccountAttributes(signalingKey, registrationId, true);
   }
 
   @Override