diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 5e15d318c5..f4bf7b48ad 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -205,6 +205,7 @@
+
getPreKeys(Context context, MasterSecret masterSecret) {
- List records = new LinkedList();
- File directory = getPreKeysDirectory(context);
- String[] keyRecordIds = directory.list();
-
- Arrays.sort(keyRecordIds, new PreKeyRecordIdComparator());
-
- for (String keyRecordId : keyRecordIds) {
- try {
- if (!keyRecordId.equals(PreKeyIndex.FILE_NAME) && Integer.parseInt(keyRecordId) != Medium.MAX_VALUE) {
- records.add(new PreKeyRecord(context, masterSecret, Integer.parseInt(keyRecordId)));
- }
- } catch (InvalidKeyIdException e) {
- Log.w("PreKeyUtil", e);
- new File(getPreKeysDirectory(context), keyRecordId).delete();
- } catch (NumberFormatException nfe) {
- Log.w("PreKeyUtil", nfe);
- new File(getPreKeysDirectory(context), keyRecordId).delete();
- }
- }
-
- return records;
- }
-
- public static void clearPreKeys(Context context) {
- File directory = getPreKeysDirectory(context);
- String[] keyRecords = directory.list();
-
- for (String keyRecord : keyRecords) {
- new File(directory, keyRecord).delete();
- }
- }
+// public static List getPreKeys(Context context, MasterSecret masterSecret) {
+// List records = new LinkedList();
+// File directory = getPreKeysDirectory(context);
+// String[] keyRecordIds = directory.list();
+//
+// Arrays.sort(keyRecordIds, new PreKeyRecordIdComparator());
+//
+// for (String keyRecordId : keyRecordIds) {
+// try {
+// if (!keyRecordId.equals(PreKeyIndex.FILE_NAME) && Integer.parseInt(keyRecordId) != Medium.MAX_VALUE) {
+// records.add(new PreKeyRecord(context, masterSecret, Integer.parseInt(keyRecordId)));
+// }
+// } catch (InvalidKeyIdException e) {
+// Log.w("PreKeyUtil", e);
+// new File(getPreKeysDirectory(context), keyRecordId).delete();
+// } catch (NumberFormatException nfe) {
+// Log.w("PreKeyUtil", nfe);
+// new File(getPreKeysDirectory(context), keyRecordId).delete();
+// }
+// }
+//
+// return records;
+// }
+//
+// public static void clearPreKeys(Context context) {
+// File directory = getPreKeysDirectory(context);
+// String[] keyRecords = directory.list();
+//
+// for (String keyRecord : keyRecords) {
+// new File(directory, keyRecord).delete();
+// }
+// }
private static void setNextPreKeyId(Context context, int id) {
try {
@@ -126,7 +125,7 @@ public class PreKeyUtil {
try {
File nextFile = new File(getPreKeysDirectory(context), PreKeyIndex.FILE_NAME);
- if (nextFile.exists()) {
+ if (!nextFile.exists()) {
return Util.getSecureRandom().nextInt(Medium.MAX_VALUE);
} else {
InputStreamReader reader = new InputStreamReader(new FileInputStream(nextFile));
diff --git a/library/src/org/whispersystems/textsecure/push/PreKeyStatus.java b/library/src/org/whispersystems/textsecure/push/PreKeyStatus.java
new file mode 100644
index 0000000000..1a361e1fae
--- /dev/null
+++ b/library/src/org/whispersystems/textsecure/push/PreKeyStatus.java
@@ -0,0 +1,12 @@
+package org.whispersystems.textsecure.push;
+
+public class PreKeyStatus {
+
+ private int count;
+
+ public PreKeyStatus() {}
+
+ public int getCount() {
+ return count;
+ }
+}
diff --git a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java
index 360266d0c7..abae23f565 100644
--- a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java
+++ b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java
@@ -40,6 +40,7 @@ public class PushServiceSocket {
private static final String CREATE_ACCOUNT_VOICE_PATH = "/v1/accounts/voice/code/%s";
private static final String VERIFY_ACCOUNT_PATH = "/v1/accounts/code/%s";
private static final String REGISTER_GCM_PATH = "/v1/accounts/gcm/";
+ private static final String PREKEY_METADATA_PATH = "/v1/keys/";
private static final String PREKEY_PATH = "/v1/keys/%s";
private static final String PREKEY_DEVICE_PATH = "/v1/keys/%s/%s";
@@ -123,6 +124,13 @@ public class PushServiceSocket {
PreKeyList.toJson(new PreKeyList(lastResortEntity, entities)));
}
+ public int getAvailablePreKeys() throws IOException {
+ String responseText = makeRequest(PREKEY_METADATA_PATH, "GET", null);
+ PreKeyStatus preKeyStatus = new Gson().fromJson(responseText, PreKeyStatus.class);
+
+ return preKeyStatus.getCount();
+ }
+
public List getPreKeys(PushAddress destination) throws IOException {
try {
String deviceId = String.valueOf(destination.getDeviceId());
diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java
index f1ade383cd..2bbf886a82 100644
--- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java
+++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java
@@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessageV2;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
+import org.thoughtcrime.securesms.service.PreKeyService;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@@ -121,6 +122,8 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
PreKeyRecord.delete(context, preKeyId);
}
+ PreKeyService.initiateRefresh(context, masterSecret);
+
DatabaseFactory.getIdentityDatabase(context)
.saveIdentity(masterSecret, recipientDevice.getRecipientId(), theirIdentityKey);
}
diff --git a/src/org/thoughtcrime/securesms/service/PreKeyService.java b/src/org/thoughtcrime/securesms/service/PreKeyService.java
new file mode 100644
index 0000000000..9296eb51de
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/service/PreKeyService.java
@@ -0,0 +1,90 @@
+package org.thoughtcrime.securesms.service;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+
+import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
+import org.thoughtcrime.securesms.push.PushServiceSocketFactory;
+import org.thoughtcrime.securesms.util.TextSecurePreferences;
+import org.whispersystems.textsecure.crypto.IdentityKey;
+import org.whispersystems.textsecure.crypto.MasterSecret;
+import org.whispersystems.textsecure.crypto.PreKeyUtil;
+import org.whispersystems.textsecure.crypto.ecc.Curve;
+import org.whispersystems.textsecure.push.PushServiceSocket;
+import org.whispersystems.textsecure.storage.PreKeyRecord;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+public class PreKeyService extends Service {
+
+ private static final String TAG = PreKeyService.class.getSimpleName();
+ public static final String REFRESH_ACTION = "org.thoughtcrime.securesms.PreKeyService.REFRESH";
+
+ private static final int PREKEY_MINIMUM = 10;
+
+ private final Executor executor = Executors.newSingleThreadExecutor();
+
+ public static void initiateRefresh(Context context, MasterSecret masterSecret) {
+ Intent intent = new Intent(context, PreKeyService.class);
+ intent.setAction(PreKeyService.REFRESH_ACTION);
+ intent.putExtra("master_secret", masterSecret);
+ context.startService(intent);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flats, int startId) {
+ if (REFRESH_ACTION.equals(intent.getAction())) {
+ MasterSecret masterSecret = intent.getParcelableExtra("master_secret");
+ executor.execute(new RefreshTask(this, masterSecret));
+ }
+
+ return START_NOT_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ private static class RefreshTask implements Runnable {
+
+ private final Context context;
+ private final MasterSecret masterSecret;
+
+ public RefreshTask(Context context, MasterSecret masterSecret) {
+ this.context = context.getApplicationContext();
+ this.masterSecret = masterSecret;
+ }
+
+ public void run() {
+ try {
+ if (!TextSecurePreferences.isPushRegistered(context)) return;
+
+ PushServiceSocket socket = PushServiceSocketFactory.create(context);
+ int availableKeys = socket.getAvailablePreKeys();
+
+ if (availableKeys >= PREKEY_MINIMUM) {
+ Log.w(TAG, "Available keys sufficient: " + availableKeys);
+ return;
+ }
+
+ List preKeyRecords = PreKeyUtil.generatePreKeys(context, masterSecret);
+ PreKeyRecord lastResortKeyRecord = PreKeyUtil.generateLastResortKey(context, masterSecret);
+ IdentityKey identityKey = IdentityKeyUtil.getIdentityKey(context, Curve.DJB_TYPE);
+
+ Log.w(TAG, "Registering new prekeys...");
+
+ socket.registerPreKeys(identityKey, lastResortKeyRecord, preKeyRecords);
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ }
+ }
+ }
+
+}
diff --git a/src/org/thoughtcrime/securesms/service/RegistrationService.java b/src/org/thoughtcrime/securesms/service/RegistrationService.java
index e56def7f34..ad69f5f890 100644
--- a/src/org/thoughtcrime/securesms/service/RegistrationService.java
+++ b/src/org/thoughtcrime/securesms/service/RegistrationService.java
@@ -68,7 +68,6 @@ public class RegistrationService extends Service {
public static final String GCM_REGISTRATION_ID = "GCMRegistrationId";
private static final long REGISTRATION_TIMEOUT_MILLIS = 120000;
- private static final Object GENERATING_PREKEYS_SEMAPHOR = new Object();
private final ExecutorService executor = Executors.newSingleThreadExecutor();
private final Binder binder = new RegistrationServiceBinder();
@@ -144,27 +143,6 @@ public class RegistrationService extends Service {
registerReceiver(gcmRegistrationReceiver, filter);
}
- private void initializePreKeyGenerator(final MasterSecret masterSecret) {
- synchronized (GENERATING_PREKEYS_SEMAPHOR) {
- if (generatingPreKeys) return;
- else generatingPreKeys = true;
- }
-
- new Thread() {
- public void run() {
- if (PreKeyUtil.getPreKeys(RegistrationService.this, masterSecret).size() < PreKeyUtil.BATCH_SIZE) {
- PreKeyUtil.generatePreKeys(RegistrationService.this, masterSecret);
- PreKeyUtil.generateLastResortKey(RegistrationService.this, masterSecret);
- }
-
- synchronized (GENERATING_PREKEYS_SEMAPHOR) {
- generatingPreKeys = false;
- GENERATING_PREKEYS_SEMAPHOR.notifyAll();
- }
- }
- }.start();
- }
-
private synchronized void shutdownChallengeListener() {
if (challengeReceiver != null) {
unregisterReceiver(challengeReceiver);
@@ -195,7 +173,6 @@ public class RegistrationService extends Service {
try {
initializeGcmRegistrationListener();
- initializePreKeyGenerator(masterSecret);
PushServiceSocket socket = PushServiceSocketFactory.create(this, number, password);
@@ -240,8 +217,6 @@ public class RegistrationService extends Service {
initializeChallengeListener();
initializeGcmRegistrationListener();
- initializePreKeyGenerator(masterSecret);
-
setState(new RegistrationState(RegistrationState.STATE_CONNECTING, number));
PushServiceSocket socket = PushServiceSocketFactory.create(this, number, password);
@@ -287,7 +262,7 @@ public class RegistrationService extends Service {
{
setState(new RegistrationState(RegistrationState.STATE_GENERATING_KEYS, number));
IdentityKey identityKey = IdentityKeyUtil.getIdentityKey(this, Curve.DJB_TYPE);
- List records = waitForPreKeys(masterSecret);
+ List records = PreKeyUtil.generatePreKeys(this, masterSecret);
PreKeyRecord lastResort = PreKeyUtil.generateLastResortKey(this, masterSecret);
socket.registerPreKeys(identityKey, lastResort, records);
@@ -333,20 +308,6 @@ public class RegistrationService extends Service {
return this.gcmRegistrationId;
}
- private List waitForPreKeys(MasterSecret masterSecret) {
- synchronized (GENERATING_PREKEYS_SEMAPHOR) {
- while (generatingPreKeys) {
- try {
- GENERATING_PREKEYS_SEMAPHOR.wait();
- } catch (InterruptedException e) {
- throw new AssertionError(e);
- }
- }
- }
-
- return PreKeyUtil.getPreKeys(this, masterSecret);
- }
-
private synchronized void challengeReceived(String challenge) {
this.challenge = challenge;
notifyAll();