diff --git a/README.md b/README.md index 459a1abdbe4..c0c7f6f7b76 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,13 @@ Currently available on the Play store. Get it on Google Play +#WebSocket Support# + +This branch adds rudimentary WebSocket-ONLY support to Signal Private Messenger. +In order to build a modified version of libtextsecure is needed, for that [checkout](https://github.com/JavaJens/libtextsecure-java/tree/fix/maven_local) my fork + +and run: ````./gradlew tasks installArchives```` to install in local maven directory. + ## Contributing Bug reports We use GitHub for bug tracking. Please search the existing issues for your bug and create a new one if the issue is not yet tracked! diff --git a/build.gradle b/build.gradle index c422205bbcd..70fa66832d9 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,6 @@ buildscript { repositories { + mavenLocal() maven { url "https://repo1.maven.org/maven2" } @@ -72,7 +73,7 @@ dependencies { compile 'org.whispersystems:jobmanager:1.0.2' compile 'org.whispersystems:libpastelog:1.0.7' compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' - compile 'org.whispersystems:textsecure-android:1.8.6' + compile 'org.whispersystems:textsecure-android:2.8.6' compile 'com.h6ah4i.android.compat:mulsellistprefcompat:1.0.0' compile 'com.google.zxing:core:3.2.1' @@ -165,11 +166,12 @@ android { buildConfigField "long", "BUILD_TIMESTAMP", System.currentTimeMillis() + "L" buildConfigField "String", "TEXTSECURE_URL", "\"https://textsecure-service.whispersystems.org\"" - buildConfigField "String", "USER_AGENT", "\"OWA\"" + buildConfigField "String", "USER_AGENT", "\"JEN\"" buildConfigField "String", "REDPHONE_MASTER_URL", "\"https://redphone-master.whispersystems.org\"" buildConfigField "String", "REDPHONE_RELAY_HOST", "\"relay.whispersystems.org\"" buildConfigField "String", "REDPHONE_PREFIX_NAME", "\".whispersystems.org\"" buildConfigField "boolean", "DEV_BUILD", "false" + buildConfigField "boolean", "FORCE_WEBSOCKETS", "false" } compileOptions { @@ -216,9 +218,24 @@ android { dev.initWith(buildTypes.debug) dev { buildConfigField "boolean", "DEV_BUILD", "true" + versionNameSuffix "-dev" + } + websockets.initWith(buildTypes.dev) + websockets { + buildConfigField "boolean", "FORCE_WEBSOCKETS", "true" } } + productFlavors { + prod { + // defaults + } + staging { + buildConfigField "String", "TEXTSECURE_URL", "\"https://textsecure-service-staging.whispersystems.org\"" + buildConfigField "String", "REDPHONE_MASTER_URL", "\"https://redphone-staging.whispersystems.org\"" + buildConfigField "String", "REDPHONE_RELAY_HOST", "\"redphone-staging-relay.whispersystems.org\"" + } + } sourceSets { main { manifest.srcFile 'AndroidManifest.xml' diff --git a/res/values/strings.xml b/res/values/strings.xml index cf6bd55c849..2d74297c884 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -474,11 +474,14 @@ than 4.0 must have a registered Google Account. Devices running Android 4.0 or newer do not require a Google account, but must have the Play Store app installed. + "This device setup is not officially supported, and if you experience bugs you will have to resolve them yourself." + Double-check that this is your number! We\'re about to verify it with an SMS. Continue Edit + I Understand Possible problems diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java index aaab48328d8..e6eea5f30cb 100644 --- a/src/org/thoughtcrime/securesms/ApplicationContext.java +++ b/src/org/thoughtcrime/securesms/ApplicationContext.java @@ -21,6 +21,7 @@ import android.os.StrictMode; import android.os.StrictMode.ThreadPolicy; import android.os.StrictMode.VmPolicy; +import android.content.Intent; import org.thoughtcrime.securesms.crypto.PRNGFixes; import org.thoughtcrime.securesms.dependencies.AxolotlStorageModule; @@ -39,6 +40,7 @@ import org.whispersystems.jobqueue.requirements.NetworkRequirementProvider; import org.whispersystems.libaxolotl.logging.AxolotlLoggerProvider; import org.whispersystems.libaxolotl.util.AndroidAxolotlLogger; +import org.thoughtcrime.securesms.service.MessageRetrievalService; import dagger.ObjectGraph; @@ -125,10 +127,14 @@ private void initializeDependencyInjection() { } private void initializeGcmCheck() { - if (TextSecurePreferences.isPushRegistered(this) && + if (TextSecurePreferences.isGcmRegistered(this) && TextSecurePreferences.getGcmRegistrationId(this) == null) { this.jobManager.add(new GcmRefreshJob(this)); + } else if (!TextSecurePreferences.isGcmRegistered(this) && + TextSecurePreferences.isPushRegistered(this)) + { + startService(new Intent(this, MessageRetrievalService.class)); } } diff --git a/src/org/thoughtcrime/securesms/RegistrationActivity.java b/src/org/thoughtcrime/securesms/RegistrationActivity.java index 61b94f92f60..45261ac6f1c 100644 --- a/src/org/thoughtcrime/securesms/RegistrationActivity.java +++ b/src/org/thoughtcrime/securesms/RegistrationActivity.java @@ -24,6 +24,7 @@ import com.google.i18n.phonenumbers.PhoneNumberUtil; import com.google.i18n.phonenumbers.Phonenumber; +import org.thoughtcrime.securesms.BuildConfig; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.util.Dialogs; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -196,34 +197,51 @@ public void onClick(View v) { int gcmStatus = GooglePlayServicesUtil.isGooglePlayServicesAvailable(self); if (gcmStatus != ConnectionResult.SUCCESS) { - if (GooglePlayServicesUtil.isUserRecoverableError(gcmStatus)) { + if(BuildConfig.FORCE_WEBSOCKETS) { + AlertDialog.Builder unsupportedDialog = new AlertDialog.Builder(self); + unsupportedDialog.setTitle(getString(R.string.RegistrationActivity_unsupported)); + unsupportedDialog.setMessage(getString(R.string.RegistrationActivity_websockets_only_unsupported)); + unsupportedDialog.setPositiveButton(getString(R.string.RegistrationActivity_I_understand), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + showDoubleCheckDialog(self,e164number); + } + }); + unsupportedDialog.show(); + } else if (GooglePlayServicesUtil.isUserRecoverableError(gcmStatus)) { GooglePlayServicesUtil.getErrorDialog(gcmStatus, self, 9000).show(); + return; } else { Dialogs.showAlertDialog(self, getString(R.string.RegistrationActivity_unsupported), getString(R.string.RegistrationActivity_sorry_this_device_is_not_supported_for_data_messaging)); + return; } - return; + } else { + showDoubleCheckDialog(self,e164number); } - - AlertDialog.Builder dialog = new AlertDialog.Builder(self); - dialog.setTitle(PhoneNumberFormatter.getInternationalFormatFromE164(e164number)); - dialog.setMessage(R.string.RegistrationActivity_we_will_now_verify_that_the_following_number_is_associated_with_your_device_s); - dialog.setPositiveButton(getString(R.string.RegistrationActivity_continue), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - Intent intent = new Intent(self, RegistrationProgressActivity.class); - intent.putExtra("e164number", e164number); - intent.putExtra("master_secret", masterSecret); - startActivity(intent); - finish(); - } - }); - dialog.setNegativeButton(getString(R.string.RegistrationActivity_edit), null); - dialog.show(); } } + private void showDoubleCheckDialog(final RegistrationActivity self, final String e164number){ + AlertDialog.Builder dialog = new AlertDialog.Builder(self); + dialog.setTitle(PhoneNumberFormatter.getInternationalFormatFromE164(e164number)); + dialog.setMessage(R.string.RegistrationActivity_we_will_now_verify_that_the_following_number_is_associated_with_your_device_s); + dialog.setPositiveButton(getString(R.string.RegistrationActivity_continue), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent intent = new Intent(self, RegistrationProgressActivity.class); + intent.putExtra("e164number", e164number); + intent.putExtra("master_secret", masterSecret); + startActivity(intent); + finish(); + } + }); + dialog.setNegativeButton(getString(R.string.RegistrationActivity_edit), null); + dialog.show(); + } + private class CountryCodeChangedListener implements TextWatcher { @Override public void afterTextChanged(Editable s) { diff --git a/src/org/thoughtcrime/securesms/RegistrationProgressActivity.java b/src/org/thoughtcrime/securesms/RegistrationProgressActivity.java index 0e7feeee0db..f5cab96f01b 100644 --- a/src/org/thoughtcrime/securesms/RegistrationProgressActivity.java +++ b/src/org/thoughtcrime/securesms/RegistrationProgressActivity.java @@ -31,6 +31,7 @@ import android.widget.Toast; import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob; import org.thoughtcrime.securesms.push.TextSecureCommunicationFactory; import org.thoughtcrime.securesms.service.RegistrationService; import org.thoughtcrime.securesms.util.Dialogs; @@ -333,7 +334,9 @@ private void handleVerificationComplete() { } shutdownService(); - startActivity(new Intent(this, ConversationListActivity.class)); + ApplicationContext.getInstance(this) + .getJobManager() + .add(new PushNotificationReceiveJob(this)); finish(); } @@ -521,8 +524,7 @@ protected Integer doInBackground(Void... params) { TextSecureAccountManager accountManager = TextSecureCommunicationFactory.createManager(context, e164number, password); int registrationId = TextSecurePreferences.getLocalRegistrationId(context); - accountManager.verifyAccountWithCode(code, signalingKey, registrationId, true); - + accountManager.verifyAccountWithCode(code, signalingKey, true, registrationId, true); return SUCCESS; } catch (ExpectationFailedException e) { Log.w(TAG, e); diff --git a/src/org/thoughtcrime/securesms/gcm/GcmBroadcastReceiver.java b/src/org/thoughtcrime/securesms/gcm/GcmBroadcastReceiver.java index 7a37e0a236e..e1483d688ce 100644 --- a/src/org/thoughtcrime/securesms/gcm/GcmBroadcastReceiver.java +++ b/src/org/thoughtcrime/securesms/gcm/GcmBroadcastReceiver.java @@ -35,7 +35,7 @@ public void onReceive(Context context, Intent intent) { if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) { Log.w(TAG, "GCM message..."); - if (!TextSecurePreferences.isPushRegistered(context)) { + if (!TextSecurePreferences.isGcmRegistered(context)) { Log.w(TAG, "Not push registered!"); return; } diff --git a/src/org/thoughtcrime/securesms/jobs/GcmRefreshJob.java b/src/org/thoughtcrime/securesms/jobs/GcmRefreshJob.java index c704864bbba..31f3350c401 100644 --- a/src/org/thoughtcrime/securesms/jobs/GcmRefreshJob.java +++ b/src/org/thoughtcrime/securesms/jobs/GcmRefreshJob.java @@ -28,6 +28,7 @@ import com.google.android.gms.common.GooglePlayServicesUtil; import com.google.android.gms.gcm.GoogleCloudMessaging; +import org.thoughtcrime.securesms.BuildConfig; import org.thoughtcrime.redphone.signaling.RedPhoneAccountManager; import org.thoughtcrime.redphone.signaling.UnauthorizedException; import org.thoughtcrime.securesms.PlayServicesProblemActivity; @@ -66,7 +67,7 @@ public void onRun() throws Exception { Log.w(TAG, "GCM registrationId expired, reregistering..."); int result = GooglePlayServicesUtil.isGooglePlayServicesAvailable(context); - if (result != ConnectionResult.SUCCESS) { + if (result != ConnectionResult.SUCCESS || BuildConfig.FORCE_WEBSOCKETS) { notifyGcmFailure(); } else { String gcmId = GoogleCloudMessaging.getInstance(context).register(REGISTRATION_ID); @@ -79,8 +80,9 @@ public void onRun() throws Exception { } TextSecurePreferences.setGcmRegistrationId(context, gcmId); - TextSecurePreferences.setWebsocketRegistered(context, true); + TextSecurePreferences.setGcmRegistered(context, true); } + TextSecurePreferences.setWebsocketRegistered(context, true); } } diff --git a/src/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java b/src/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java index 654b3ce304e..531ca63320f 100644 --- a/src/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java +++ b/src/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java @@ -45,7 +45,7 @@ public void onRun() throws IOException { String token = textSecureAccountManager.getAccountVerificationToken(); redPhoneAccountManager.createAccount(token, new RedPhoneAccountAttributes(signalingKey, gcmRegistrationId)); - textSecureAccountManager.setAccountAttributes(signalingKey, registrationId, true); + textSecureAccountManager.setAccountAttributes(signalingKey, true, registrationId, true); } @Override diff --git a/src/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java b/src/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java index 87be7a2a276..fdffefaf594 100644 --- a/src/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java +++ b/src/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java @@ -194,20 +194,22 @@ protected Integer doInBackground(Void... params) { TextSecurePreferences.getLocalNumber(context), TextSecurePreferences.getPushServerPassword(context)); - try { - accountManager.setGcmId(Optional.absent()); - } catch (AuthorizationFailedException e) { - Log.w(TAG, e); + if (TextSecurePreferences.isGcmRegistered(context)) { + try { + accountManager.setGcmId(Optional.absent()); + } catch (AuthorizationFailedException e) { + Log.w(TAG, e); + } + + try { + redPhoneAccountManager.setGcmId(Optional.absent()); + } catch (UnauthorizedException e) { + Log.w(TAG, e); + } + + GoogleCloudMessaging.getInstance(context).unregister(); } - try { - redPhoneAccountManager.setGcmId(Optional.absent()); - } catch (UnauthorizedException e) { - Log.w(TAG, e); - } - - GoogleCloudMessaging.getInstance(context).unregister(); - return SUCCESS; } catch (IOException ioe) { Log.w(TAG, ioe); diff --git a/src/org/thoughtcrime/securesms/service/DirectoryRefreshListener.java b/src/org/thoughtcrime/securesms/service/DirectoryRefreshListener.java index 9e70ce8cd98..5e2b1c4cfc7 100644 --- a/src/org/thoughtcrime/securesms/service/DirectoryRefreshListener.java +++ b/src/org/thoughtcrime/securesms/service/DirectoryRefreshListener.java @@ -37,6 +37,9 @@ private void handleRefreshAction(Context context) { public static void schedule(Context context) { if (!TextSecurePreferences.isPushRegistered(context)) return; + if (!TextSecurePreferences.isGcmRegistered(context)) { + context.startService(new Intent(context, MessageRetrievalService.class)); + } AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(DirectoryRefreshListener.REFRESH_EVENT); diff --git a/src/org/thoughtcrime/securesms/service/MessageRetrievalService.java b/src/org/thoughtcrime/securesms/service/MessageRetrievalService.java index 1eb29e633ea..883b33fec53 100644 --- a/src/org/thoughtcrime/securesms/service/MessageRetrievalService.java +++ b/src/org/thoughtcrime/securesms/service/MessageRetrievalService.java @@ -149,8 +149,8 @@ private synchronized boolean isConnectionNecessary() { Log.w(TAG, String.format("Network requirement: %s, active activities: %s, push pending: %s", networkRequirement.isPresent(), activeActivities, pushPending.size())); - return TextSecurePreferences.isWebsocketRegistered(this) && - (activeActivities > 0 || !pushPending.isEmpty()) && + return TextSecurePreferences.isWebsocketRegistered(this) && + (activeActivities > 0 || !pushPending.isEmpty() || !TextSecurePreferences.isGcmRegistered(this)) && networkRequirement.isPresent(); } diff --git a/src/org/thoughtcrime/securesms/service/RegistrationService.java b/src/org/thoughtcrime/securesms/service/RegistrationService.java index 879aac469ae..ae108355c20 100644 --- a/src/org/thoughtcrime/securesms/service/RegistrationService.java +++ b/src/org/thoughtcrime/securesms/service/RegistrationService.java @@ -10,6 +10,8 @@ import android.os.IBinder; import android.util.Log; +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.RedPhoneAccountAttributes; @@ -203,7 +205,7 @@ private void handleSmsRegistrationIntent(Intent intent) { setState(new RegistrationState(RegistrationState.STATE_VERIFYING, number)); String challenge = waitForChallenge(); - accountManager.verifyAccountWithCode(challenge, signalingKey, registrationId, true); + accountManager.verifyAccountWithCode(challenge, signalingKey, true, registrationId, true); handleCommonRegistration(accountManager, number, password, signalingKey); markAsVerified(number, password, signalingKey); @@ -242,23 +244,33 @@ private void handleCommonRegistration(TextSecureAccountManager accountManager, S SignedPreKeyRecord signedPreKey = PreKeyUtil.generateSignedPreKey(this, identityKey); accountManager.setPreKeys(identityKey.getPublicKey(),lastResort, signedPreKey, records); - setState(new RegistrationState(RegistrationState.STATE_GCM_REGISTERING, number)); + if (GooglePlayServicesUtil.isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS && + !BuildConfig.FORCE_WEBSOCKETS) + { + setState(new RegistrationState(RegistrationState.STATE_GCM_REGISTERING, number)); - String gcmRegistrationId = GoogleCloudMessaging.getInstance(this).register(GcmRefreshJob.REGISTRATION_ID); - accountManager.setGcmId(Optional.of(gcmRegistrationId)); + String gcmRegistrationId = GoogleCloudMessaging.getInstance(this).register(GcmRefreshJob.REGISTRATION_ID); + accountManager.setGcmId(Optional.of(gcmRegistrationId)); - TextSecurePreferences.setGcmRegistrationId(this, gcmRegistrationId); + TextSecurePreferences.setGcmRegistrationId(this, gcmRegistrationId); + TextSecurePreferences.setGcmRegistered(this, true); + } TextSecurePreferences.setWebsocketRegistered(this, true); DatabaseFactory.getIdentityDatabase(this).saveIdentity(self.getRecipientId(), identityKey.getPublicKey()); DirectoryHelper.refreshDirectory(this, accountManager, number); - RedPhoneAccountManager redPhoneAccountManager = new RedPhoneAccountManager(BuildConfig.REDPHONE_MASTER_URL, - new RedPhoneTrustStore(this), - number, password); - - String verificationToken = accountManager.getAccountVerificationToken(); - redPhoneAccountManager.createAccount(verificationToken, new RedPhoneAccountAttributes(signalingKey, gcmRegistrationId)); + //if (GooglePlayServicesUtil.isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS && + // !BuildConfig.FORCE_WEBSOCKETS) + //{ + // RedPhoneAccountManager redPhoneAccountManager = new RedPhoneAccountManager(BuildConfig.REDPHONE_MASTER_URL, + // new RedPhoneTrustStore(this), + // number, password); + // String verificationToken = accountManager.getAccountVerificationToken(); + // redPhoneAccountManager.createAccount(verificationToken, new RedPhoneAccountAttributes(signalingKey, + // TextSecurePreferences.getGcmRegistrationId(this))); + //} + accountManager.getAccountVerificationToken(); DirectoryRefreshListener.schedule(this); } @@ -290,6 +302,7 @@ private void markAsVerifying(boolean verifying) { if (verifying) { TextSecurePreferences.setPushRegistered(this, false); + TextSecurePreferences.setGcmRegistered(this, false); } } diff --git a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java index 4b428d9fac9..a2aa1cd633e 100644 --- a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -66,6 +66,7 @@ public class TextSecurePreferences { private static final String LOCAL_NUMBER_PREF = "pref_local_number"; private static final String VERIFYING_STATE_PREF = "pref_verifying"; public static final String REGISTERED_GCM_PREF = "pref_gcm_registered"; + public static final String REGISTERED_PUSH_PREF = "pref_push_registered"; private static final String GCM_PASSWORD_PREF = "pref_gcm_password"; private static final String PROMPTED_PUSH_REGISTRATION_PREF = "pref_prompted_push_registration"; private static final String PROMPTED_DEFAULT_SMS_PREF = "pref_prompted_default_sms"; @@ -392,11 +393,20 @@ public static void setVerifying(Context context, boolean verifying) { } public static boolean isPushRegistered(Context context) { - return getBooleanPreference(context, REGISTERED_GCM_PREF, false); + return getBooleanPreference(context, REGISTERED_PUSH_PREF, false); } public static void setPushRegistered(Context context, boolean registered) { Log.w("TextSecurePreferences", "Setting push registered: " + registered); + setBooleanPreference(context, REGISTERED_PUSH_PREF, registered); + } + + public static boolean isGcmRegistered(Context context) { + return getBooleanPreference(context, REGISTERED_GCM_PREF, false); + } + + public static void setGcmRegistered(Context context, boolean registered) { + Log.w("TextSecurePreferences", "Setting gcm registered: " + registered); setBooleanPreference(context, REGISTERED_GCM_PREF, registered); }