diff --git a/androidTest/org/thoughtcrime/securesms/database/CanonicalAddressDatabaseTest.java b/androidTest/java/org/thoughtcrime/securesms/database/CanonicalAddressDatabaseTest.java similarity index 100% rename from androidTest/org/thoughtcrime/securesms/database/CanonicalAddressDatabaseTest.java rename to androidTest/java/org/thoughtcrime/securesms/database/CanonicalAddressDatabaseTest.java diff --git a/androidTest/java/org/thoughtcrime/securesms/service/PreKeyServiceTest.java b/androidTest/java/org/thoughtcrime/securesms/service/PreKeyServiceTest.java new file mode 100644 index 0000000000..318c71db06 --- /dev/null +++ b/androidTest/java/org/thoughtcrime/securesms/service/PreKeyServiceTest.java @@ -0,0 +1,92 @@ +package org.thoughtcrime.securesms.service; + +import android.test.AndroidTestCase; + +import org.whispersystems.libaxolotl.ecc.Curve; +import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; +import org.whispersystems.libaxolotl.state.SignedPreKeyStore; +import org.whispersystems.textsecure.push.PushServiceSocket; +import org.whispersystems.textsecure.push.SignedPreKeyEntity; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; + +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +public class PreKeyServiceTest extends AndroidTestCase { + + public void testSignedPreKeyRotationNotRegistered() throws IOException { + SignedPreKeyStore signedPreKeyStore = mock(SignedPreKeyStore.class); + PushServiceSocket pushServiceSocket = mock(PushServiceSocket.class); + + when(pushServiceSocket.getCurrentSignedPreKey()).thenReturn(null); + + PreKeyService.CleanSignedPreKeysTask cleanTask = new PreKeyService.CleanSignedPreKeysTask(signedPreKeyStore, + pushServiceSocket); + + cleanTask.run(); + + verify(pushServiceSocket).getCurrentSignedPreKey(); + verifyNoMoreInteractions(signedPreKeyStore); + } + + public void testSignedPreKeyEviction() throws Exception { + SignedPreKeyStore signedPreKeyStore = mock(SignedPreKeyStore.class); + PushServiceSocket pushServiceSocket = mock(PushServiceSocket.class); + SignedPreKeyEntity currentSignedPreKeyEntity = mock(SignedPreKeyEntity.class); + + when(currentSignedPreKeyEntity.getKeyId()).thenReturn(3133); + when(pushServiceSocket.getCurrentSignedPreKey()).thenReturn(currentSignedPreKeyEntity); + + final SignedPreKeyRecord currentRecord = new SignedPreKeyRecord(3133, System.currentTimeMillis(), Curve.generateKeyPair(true), new byte[64]); + + List records = new LinkedList() {{ + add(new SignedPreKeyRecord(1, 10, Curve.generateKeyPair(true), new byte[32])); + add(new SignedPreKeyRecord(2, 11, Curve.generateKeyPair(true), new byte[32])); + add(new SignedPreKeyRecord(3, System.currentTimeMillis() - 90, Curve.generateKeyPair(true), new byte[64])); + add(new SignedPreKeyRecord(4, System.currentTimeMillis() - 100, Curve.generateKeyPair(true), new byte[64])); + add(currentRecord); + }}; + + when(signedPreKeyStore.loadSignedPreKeys()).thenReturn(records); + when(signedPreKeyStore.loadSignedPreKey(eq(3133))).thenReturn(currentRecord); + + PreKeyService.CleanSignedPreKeysTask cleanTask = new PreKeyService.CleanSignedPreKeysTask(signedPreKeyStore, pushServiceSocket); + cleanTask.run(); + + verify(signedPreKeyStore).removeSignedPreKey(eq(1)); + verify(signedPreKeyStore).removeSignedPreKey(eq(2)); + verify(signedPreKeyStore, times(2)).removeSignedPreKey(anyInt()); + } + + public void testSignedPreKeyNoEviction() throws Exception { + SignedPreKeyStore signedPreKeyStore = mock(SignedPreKeyStore.class); + PushServiceSocket pushServiceSocket = mock(PushServiceSocket.class); + SignedPreKeyEntity currentSignedPreKeyEntity = mock(SignedPreKeyEntity.class); + + when(currentSignedPreKeyEntity.getKeyId()).thenReturn(3133); + when(pushServiceSocket.getCurrentSignedPreKey()).thenReturn(currentSignedPreKeyEntity); + + final SignedPreKeyRecord currentRecord = new SignedPreKeyRecord(3133, System.currentTimeMillis(), Curve.generateKeyPair(true), new byte[64]); + + List records = new LinkedList() {{ + add(currentRecord); + }}; + + when(signedPreKeyStore.loadSignedPreKeys()).thenReturn(records); + when(signedPreKeyStore.loadSignedPreKey(eq(3133))).thenReturn(currentRecord); + + PreKeyService.CleanSignedPreKeysTask cleanTask = new PreKeyService.CleanSignedPreKeysTask(signedPreKeyStore, pushServiceSocket); + cleanTask.run(); + + verify(signedPreKeyStore, never()).removeSignedPreKey(anyInt()); + } +} diff --git a/androidTest/org/thoughtcrime/securesms/util/PhoneNumberFormatterTest.java b/androidTest/java/org/thoughtcrime/securesms/util/PhoneNumberFormatterTest.java similarity index 100% rename from androidTest/org/thoughtcrime/securesms/util/PhoneNumberFormatterTest.java rename to androidTest/java/org/thoughtcrime/securesms/util/PhoneNumberFormatterTest.java diff --git a/androidTest/org/thoughtcrime/securesms/util/UtilTest.java b/androidTest/java/org/thoughtcrime/securesms/util/UtilTest.java similarity index 100% rename from androidTest/org/thoughtcrime/securesms/util/UtilTest.java rename to androidTest/java/org/thoughtcrime/securesms/util/UtilTest.java diff --git a/build.gradle b/build.gradle index cb48867c20..a36eb6f371 100644 --- a/build.gradle +++ b/build.gradle @@ -40,6 +40,9 @@ dependencies { androidTestCompile 'com.squareup:fest-android:1.0.8' compile project(':library') + + androidTestCompile 'com.google.dexmaker:dexmaker:1.1' + androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.1' } dependencyVerification { @@ -84,10 +87,10 @@ android { assets.srcDirs = ['assets'] } androidTest { - java.srcDirs = ['androidTest'] - resources.srcDirs = ['androidTest'] - aidl.srcDirs = ['androidTest'] - renderscript.srcDirs = ['androidTest'] + java.srcDirs = ['androidTest/java'] + resources.srcDirs = ['androidTest/java'] + aidl.srcDirs = ['androidTest/java'] + renderscript.srcDirs = ['androidTest/java'] } } } diff --git a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java index 44c9a1b771..3ff808c1d0 100644 --- a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java +++ b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java @@ -70,6 +70,7 @@ public class PushServiceSocket { private static final String PREKEY_METADATA_PATH = "/v2/keys/"; private static final String PREKEY_PATH = "/v2/keys/%s"; private static final String PREKEY_DEVICE_PATH = "/v2/keys/%s/%s"; + private static final String SIGNED_PREKEY_PATH = "/v2/keys/signed"; private static final String DIRECTORY_TOKENS_PATH = "/v1/directory/tokens"; private static final String DIRECTORY_VERIFY_PATH = "/v1/directory/%s"; @@ -251,6 +252,23 @@ public class PushServiceSocket { } } + public SignedPreKeyEntity getCurrentSignedPreKey() throws IOException { + try { + String responseText = makeRequest(SIGNED_PREKEY_PATH, "GET", null); + return SignedPreKeyEntity.fromJson(responseText); + } catch (NotFoundException e) { + Log.w("PushServiceSocket", e); + return null; + } + } + + public void setCurrentSignedPreKey(SignedPreKeyRecord signedPreKey) throws IOException { + SignedPreKeyEntity signedPreKeyEntity = new SignedPreKeyEntity(signedPreKey.getId(), + signedPreKey.getKeyPair().getPublicKey(), + signedPreKey.getSignature()); + makeRequest(SIGNED_PREKEY_PATH, "PUT", SignedPreKeyEntity.toJson(signedPreKeyEntity)); + } + public long sendAttachment(PushAttachmentData attachment) throws IOException { String response = makeRequest(String.format(ATTACHMENT_PATH, ""), "GET", null); AttachmentDescriptor attachmentKey = new Gson().fromJson(response, AttachmentDescriptor.class); diff --git a/library/src/org/whispersystems/textsecure/push/SignedPreKeyEntity.java b/library/src/org/whispersystems/textsecure/push/SignedPreKeyEntity.java index 294534d594..8714a6e9fc 100644 --- a/library/src/org/whispersystems/textsecure/push/SignedPreKeyEntity.java +++ b/library/src/org/whispersystems/textsecure/push/SignedPreKeyEntity.java @@ -30,6 +30,16 @@ public class SignedPreKeyEntity extends PreKeyEntity { return signature; } + public static String toJson(SignedPreKeyEntity entity) { + GsonBuilder builder = new GsonBuilder(); + return forBuilder(builder).create().toJson(entity); + } + + public static SignedPreKeyEntity fromJson(String serialized) { + GsonBuilder builder = new GsonBuilder(); + return forBuilder(builder).create().fromJson(serialized, SignedPreKeyEntity.class); + } + public static GsonBuilder forBuilder(GsonBuilder builder) { return PreKeyEntity.forBuilder(builder) .registerTypeAdapter(byte[].class, new ByteArrayJsonAdapter()); diff --git a/src/org/thoughtcrime/securesms/RoutingActivity.java b/src/org/thoughtcrime/securesms/RoutingActivity.java index ee9bc96f54..fcccd35e59 100644 --- a/src/org/thoughtcrime/securesms/RoutingActivity.java +++ b/src/org/thoughtcrime/securesms/RoutingActivity.java @@ -10,6 +10,7 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.service.GcmRegistrationService; +import org.thoughtcrime.securesms.service.PreKeyService; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.textsecure.crypto.MasterSecret; @@ -17,9 +18,11 @@ public class RoutingActivity extends PassphraseRequiredSherlockActivity { private static final int STATE_CREATE_PASSPHRASE = 1; private static final int STATE_PROMPT_PASSPHRASE = 2; + private static final int STATE_CONVERSATION_OR_LIST = 3; private static final int STATE_UPGRADE_DATABASE = 4; private static final int STATE_PROMPT_PUSH_REGISTRATION = 5; + private static final int STATE_CREATE_SIGNED_PREKEY = 6; private MasterSecret masterSecret = null; private boolean isVisible = false; @@ -119,14 +122,13 @@ public class RoutingActivity extends PassphraseRequiredSherlockActivity { final ConversationParameters parameters = getConversationParameters(); final Intent intent; - scheduleRefreshActions(); - if (isShareAction()) { - intent = getShareIntent(parameters); - } else if (parameters.recipients != null) { - intent = getConversationIntent(parameters); - } else { - intent = getConversationListIntent(); + if (isShareAction()) intent = getShareIntent(parameters); + else if (parameters.recipients != null) intent = getConversationIntent(parameters); + else intent = getConversationListIntent(); + + if (!TextSecurePreferences.isSignedPreKeyRegistered(this)) { + PreKeyService.initiateCreateSigned(this, masterSecret); } startActivity(intent); diff --git a/src/org/thoughtcrime/securesms/service/PreKeyService.java b/src/org/thoughtcrime/securesms/service/PreKeyService.java index 19265d2ad9..417210ffb7 100644 --- a/src/org/thoughtcrime/securesms/service/PreKeyService.java +++ b/src/org/thoughtcrime/securesms/service/PreKeyService.java @@ -10,26 +10,30 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.push.PushServiceSocketFactory; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libaxolotl.IdentityKeyPair; +import org.whispersystems.libaxolotl.InvalidKeyIdException; +import org.whispersystems.libaxolotl.state.PreKeyRecord; import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; import org.whispersystems.libaxolotl.state.SignedPreKeyStore; -import org.whispersystems.libaxolotl.state.PreKeyRecord; import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.PreKeyUtil; import org.whispersystems.textsecure.push.PushServiceSocket; +import org.whispersystems.textsecure.push.SignedPreKeyEntity; import org.whispersystems.textsecure.storage.TextSecurePreKeyStore; import java.io.IOException; import java.util.Arrays; import java.util.Comparator; -import java.util.Iterator; +import java.util.LinkedList; 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 String TAG = PreKeyService.class.getSimpleName(); + public static final String REFRESH_ACTION = "org.thoughtcrime.securesms.PreKeyService.REFRESH"; + public static final String CLEAN_ACTION = "org.thoughtcrime.securesms.PreKeyService.CLEAN"; + public static final String CREATE_SIGNED_ACTION = "org.thoughtcrime.securesms.PreKeyService.CREATE_SIGNED"; private static final int PREKEY_MINIMUM = 10; @@ -42,11 +46,36 @@ public class PreKeyService extends Service { context.startService(intent); } + public static void initiateClean(Context context, MasterSecret masterSecret) { + Intent intent = new Intent(context, PreKeyService.class); + intent.setAction(PreKeyService.CLEAN_ACTION); + intent.putExtra("master_secret", masterSecret); + context.startService(intent); + } + + public static void initiateCreateSigned(Context context, MasterSecret masterSecret) { + Intent intent = new Intent(context, PreKeyService.class); + intent.setAction(PreKeyService.CREATE_SIGNED_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)); + if (intent == null) return START_NOT_STICKY; + if (intent.getAction() == null) return START_NOT_STICKY; + + MasterSecret masterSecret = intent.getParcelableExtra("master_secret"); + + if (masterSecret == null) { + Log.w(TAG, "No master secret!"); + return START_NOT_STICKY; + } + + switch (intent.getAction()) { + case REFRESH_ACTION: executor.execute(new RefreshTask(this, masterSecret)); break; + case CLEAN_ACTION: executor.execute(new CleanSignedPreKeysTask(this, masterSecret)); break; + case CREATE_SIGNED_ACTION: executor.execute(new CreateSignedPreKeyTask(this, masterSecret)); break; } return START_NOT_STICKY; @@ -57,6 +86,98 @@ public class PreKeyService extends Service { return null; } + private static class CreateSignedPreKeyTask implements Runnable { + + private final Context context; + private final MasterSecret masterSecret; + + public CreateSignedPreKeyTask(Context context, MasterSecret masterSecret) { + this.context = context; + this.masterSecret = masterSecret; + } + + @Override + public void run() { + if (TextSecurePreferences.isSignedPreKeyRegistered(context)) { + Log.w(TAG, "Signed prekey already registered..."); + return; + } + + try { + IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); + SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, masterSecret, identityKeyPair); + PushServiceSocket socket = PushServiceSocketFactory.create(context); + + socket.setCurrentSignedPreKey(signedPreKeyRecord); + TextSecurePreferences.setSignedPreKeyRegistered(context, true); + } catch (IOException e) { + Log.w(TAG, e); + } + } + } + + static class CleanSignedPreKeysTask implements Runnable { + + private final SignedPreKeyStore signedPreKeyStore; + private final PushServiceSocket socket; + + public CleanSignedPreKeysTask(Context context, MasterSecret masterSecret) { + this(new TextSecurePreKeyStore(context, masterSecret), PushServiceSocketFactory.create(context)); + } + + public CleanSignedPreKeysTask(SignedPreKeyStore signedPreKeyStore, PushServiceSocket socket) { + this.signedPreKeyStore = signedPreKeyStore; + this.socket = socket; + } + + public void run() { + try { + SignedPreKeyEntity currentSignedPreKey = socket.getCurrentSignedPreKey(); + + if (currentSignedPreKey == null) return; + + SignedPreKeyRecord currentRecord = signedPreKeyStore.loadSignedPreKey(currentSignedPreKey.getKeyId()); + List allRecords = signedPreKeyStore.loadSignedPreKeys(); + List oldRecords = removeCurrentRecord(allRecords, currentRecord); + SignedPreKeyRecord[] oldRecordsArray = oldRecords.toArray(new SignedPreKeyRecord[0]); + + Arrays.sort(oldRecordsArray, new SignedPreKeySorter()); + + Log.w(TAG, "Existing signed prekey record count: " + oldRecordsArray.length); + + if (oldRecordsArray.length > 3) { + long oldTimestamp = System.currentTimeMillis() - (14 * 24 * 60 * 60 * 1000); + SignedPreKeyRecord[] deletionCandidates = Arrays.copyOf(oldRecordsArray, oldRecordsArray.length - 1); + + for (SignedPreKeyRecord deletionCandidate : deletionCandidates) { + Log.w(TAG, "Old signed prekey record timestamp: " + deletionCandidate.getTimestamp()); + + if (deletionCandidate.getTimestamp() <= oldTimestamp) { + Log.w(TAG, "Remove signed prekey record: " + deletionCandidate.getId()); + signedPreKeyStore.removeSignedPreKey(deletionCandidate.getId()); + } + } + } + } catch (IOException | InvalidKeyIdException e) { + Log.w(TAG, e); + } + } + + private List removeCurrentRecord(List records, + SignedPreKeyRecord currentRecord) + { + List others = new LinkedList<>(); + + for (SignedPreKeyRecord record : records) { + if (record.getId() != currentRecord.getId()) { + others.add(record); + } + } + + return others; + } + } + private static class RefreshTask implements Runnable { private final Context context; @@ -68,15 +189,13 @@ public class PreKeyService extends Service { } public void run() { - SignedPreKeyRecord signedPreKeyRecord = null; - try { if (!TextSecurePreferences.isPushRegistered(context)) return; PushServiceSocket socket = PushServiceSocketFactory.create(context); int availableKeys = socket.getAvailablePreKeys(); - if (availableKeys >= PREKEY_MINIMUM) { + if (availableKeys >= PREKEY_MINIMUM && TextSecurePreferences.isSignedPreKeyRegistered(context)) { Log.w(TAG, "Available keys sufficient: " + availableKeys); return; } @@ -84,60 +203,27 @@ public class PreKeyService extends Service { List preKeyRecords = PreKeyUtil.generatePreKeys(context, masterSecret); PreKeyRecord lastResortKeyRecord = PreKeyUtil.generateLastResortKey(context, masterSecret); IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); - - signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, masterSecret, identityKey); + SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, masterSecret, identityKey); Log.w(TAG, "Registering new prekeys..."); socket.registerPreKeys(identityKey.getPublicKey(), lastResortKeyRecord, signedPreKeyRecord, preKeyRecords); - removeOldSignedPreKeysIfNecessary(signedPreKeyRecord); + TextSecurePreferences.setSignedPreKeyRegistered(context, true); + PreKeyService.initiateClean(context, masterSecret); } catch (IOException e) { Log.w(TAG, e); - if (signedPreKeyRecord != null) { - Log.w(TAG, "Remote store failed, removing generated device key: " + signedPreKeyRecord.getId()); - new TextSecurePreKeyStore(context, masterSecret).removeSignedPreKey(signedPreKeyRecord.getId()); - } } } + } - private void removeOldSignedPreKeysIfNecessary(SignedPreKeyRecord currentSignedPreKey) { - SignedPreKeyStore signedPreKeyStore = new TextSecurePreKeyStore(context, masterSecret); - List records = signedPreKeyStore.loadSignedPreKeys(); - Iterator iterator = records.iterator(); - - while (iterator.hasNext()) { - if (iterator.next().getId() == currentSignedPreKey.getId()) { - iterator.remove(); - } - } - - SignedPreKeyRecord[] recordsArray = (SignedPreKeyRecord[])records.toArray(); - Arrays.sort(recordsArray, new Comparator() { - @Override - public int compare(SignedPreKeyRecord lhs, SignedPreKeyRecord rhs) { - if (lhs.getTimestamp() < rhs.getTimestamp()) return -1; - else if (lhs.getTimestamp() > rhs.getTimestamp()) return 1; - else return 0; - } - }); - - Log.w(TAG, "Existing device key record count: " + recordsArray.length); - - if (recordsArray.length > 3) { - long oldTimestamp = System.currentTimeMillis() - (14 * 24 * 60 * 60 * 1000); - SignedPreKeyRecord[] oldRecords = Arrays.copyOf(recordsArray, recordsArray.length - 1); - - for (SignedPreKeyRecord oldRecord : oldRecords) { - Log.w(TAG, "Old device key record timestamp: " + oldRecord.getTimestamp()); - - if (oldRecord.getTimestamp() <= oldTimestamp) { - Log.w(TAG, "Remove device key record: " + oldRecord.getId()); - signedPreKeyStore.removeSignedPreKey(oldRecord.getId()); - } - } - } + private static class SignedPreKeySorter implements Comparator { + @Override + public int compare(SignedPreKeyRecord lhs, SignedPreKeyRecord rhs) { + if (lhs.getTimestamp() < rhs.getTimestamp()) return -1; + else if (lhs.getTimestamp() > rhs.getTimestamp()) return 1; + else return 0; } } diff --git a/src/org/thoughtcrime/securesms/service/RegistrationService.java b/src/org/thoughtcrime/securesms/service/RegistrationService.java index 70ae0335c0..8685ac6bf3 100644 --- a/src/org/thoughtcrime/securesms/service/RegistrationService.java +++ b/src/org/thoughtcrime/securesms/service/RegistrationService.java @@ -281,6 +281,7 @@ public class RegistrationService extends Service { TextSecurePreferences.setLocalNumber(this, number); TextSecurePreferences.setPushServerPassword(this, password); TextSecurePreferences.setSignalingKey(this, signalingKey); + TextSecurePreferences.setSignedPreKeyRegistered(this, true); } private void setState(RegistrationState state) { diff --git a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java index 669d9e5714..57abaab924 100644 --- a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -51,6 +51,15 @@ public class TextSecurePreferences { private static final String FALLBACK_SMS_ALLOWED_PREF = "pref_allow_sms_traffic_out"; private static final String FALLBACK_SMS_ASK_REQUIRED_PREF = "pref_sms_fallback_ask"; private static final String DIRECT_SMS_ALLOWED_PREF = "pref_sms_non_data_out"; + private static final String SIGNED_PREKEY_REGISTERED_PREF = "pref_signed_prekey_registered"; + + public static boolean isSignedPreKeyRegistered(Context context) { + return getBooleanPreference(context, SIGNED_PREKEY_REGISTERED_PREF, false); + } + + public static void setSignedPreKeyRegistered(Context context, boolean value) { + setBooleanPreference(context, SIGNED_PREKEY_REGISTERED_PREF, value); + } private static final String GCM_REGISTRATION_ID_PREF = "pref_gcm_registration_id"; private static final String GCM_REGISTRATION_ID_VERSION_PREF = "pref_gcm_registration_id_version";