Support for using Signal without Play Services

This is now possible with beta calling, so non-GCM users are a
part of beta calling by default.

// FREEBIE
This commit is contained in:
Moxie Marlinspike 2017-02-20 12:00:03 -08:00
parent 4112f23f33
commit 1669731329
15 changed files with 354 additions and 114 deletions

View File

@ -84,6 +84,7 @@
<uses-permission android:name="android.permission.READ_CALL_STATE"/>
<uses-permission android:name="android.permission.READ_LOGS"/>
<uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<permission android:name="org.thoughtcrime.securesms.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
@ -533,6 +534,12 @@
</intent-filter>
</receiver>
<receiver android:name=".service.PersistentConnectionBootListener">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
<receiver android:name=".notifications.MessageNotifier$ReminderReceiver">
<intent-filter>
<action android:name="org.thoughtcrime.securesms.MessageNotifier.REMINDER_ACTION"/>

View File

@ -57,7 +57,7 @@ dependencies {
compile 'org.whispersystems:jobmanager:1.0.2'
compile 'org.whispersystems:libpastelog:1.0.7'
compile 'org.whispersystems:signal-service-android:2.5.0'
compile 'org.whispersystems:signal-service-android:2.5.1'
compile 'org.whispersystems:webrtc-android:M56-S1'
compile "me.leolin:ShortcutBadger:1.10-WS1"
@ -127,7 +127,7 @@ dependencyVerification {
'com.google.android.gms:play-services-places:abf3a4a3b146ec7e6e753be62775e512868cf37d6f88ffe2d81167b33b57132b',
'org.whispersystems:jobmanager:506f679fc2fcf7bb6d10f00f41d6f6ea0abf75c70dc95b913398661ad538a181',
'org.whispersystems:libpastelog:bb331d9a98240fc139101128ba836c1edec3c40e000597cdbb29ebf4cbf34d88',
'org.whispersystems:signal-service-android:f207fcf8f17b5a1f04053151cad518f9520f8fbfb2e5563a19828f6b2c2b7b6d',
'org.whispersystems:signal-service-android:b0329b155cc9ad5e7fc08e4660538df1827c6d9485b05cab41946f36ad7ee0ab',
'org.whispersystems:webrtc-android:776a2769348f62aec28b9be6e97dcfea9c07b472ddbe24ac1ca1c46567cf8a8e',
'me.leolin:ShortcutBadger:e8e39df8a59d8211a30f40b1eeab21b3fa57b3f3e0f03abb995f82d66588778c',
'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb',
@ -162,22 +162,22 @@ dependencyVerification {
'com.google.android.gms:play-services-base:0ca636a8fc9a5af45e607cdcd61783bf5d561cbbb0f862021ce69606eee5ad49',
'com.google.android.gms:play-services-basement:95dd882c5ffba15b9a99de3fefb05d3a01946623af67454ca00055d222f85a8d',
'com.google.android.gms:play-services-iid:54e919f9957b8b7820da7ee9b83471d00d0cac1cf08ddea8b5b41aea80bb1a70',
'org.whispersystems:signal-service-java:e0b9c41fcf614d58a71afcbb290d1864275b65364ca8fafeb5fbcbdc2d59a57a',
'org.whispersystems:signal-protocol-android:1b4b9d557c8eaf861797ff683990d482d4aa8e9f23d9b17ff0cc67a02f38cb19',
'org.whispersystems:signal-service-java:910ed96e928355d118454e1dff6c11b9f95daa801f3b4022e5c8999bff47a888',
'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a',
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f',
'com.google.android.gms:play-services-tasks:69ec265168e601d0203d04cd42e34bb019b2f029aa1e16fabd38a5153eea2086',
'com.google.protobuf:protobuf-java:e0c1c64575c005601725e7c6a02cebf9e1285e888f756b2a1d73ffa8d725cc74',
'com.googlecode.libphonenumber:libphonenumber:141ebcafba7070a13d879c44e7648ddbe10beab665cb64d7b0c1bea93afb8dc2',
'com.fasterxml.jackson.core:jackson-databind:835097bcdd11f5bc8a08378c70d4c8054dfa4b911691cc2752063c75534d198d',
'com.squareup.okhttp3:okhttp:a992938d7203ca557cd7a116f002e8c427ec9cdae7ea852441abb8aec891f948',
'org.whispersystems:curve25519-android:bf6c34223d45d2f2813a8efcab9923caf99115115c760c9acea680bcb42d23c0',
'org.whispersystems:signal-protocol-java:a835cd0609cf116a74651bd0aa748db9392bba48c2d2af787757b8a1b50d131c',
'com.googlecode.libphonenumber:libphonenumber:7214dc6dfc6243e34fb1a3e02ad15331bfff6ab902d679e3ba337695265c21ca',
'com.google.protobuf:protobuf-java:e0c1c64575c005601725e7c6a02cebf9e1285e888f756b2a1d73ffa8d725cc74',
'com.fasterxml.jackson.core:jackson-databind:835097bcdd11f5bc8a08378c70d4c8054dfa4b911691cc2752063c75534d198d',
'com.squareup.okhttp3:okhttp:eecd834b09d12c3cd568b811522b97012619f7f00378c3c719a1957fac6458ef',
'org.whispersystems:curve25519-java:00f1d4919f759055f41f7853a3d475dc7c8decf0dbf045ae93414f8f23b066cc',
'com.fasterxml.jackson.core:jackson-annotations:0ca408c24202a7626ec8b861e99d85eca5e38b73311dd6dd12e3e9deecc3fe94',
'com.fasterxml.jackson.core:jackson-core:cbf4604784b4de226262845447a1ad3bb38a6728cebe86562e2c5afada8be2c0',
'com.squareup.okio:okio:8c5436cadfab36bbd97db5f5c43b7bfdb5bf2f5f894ec8709b1929f14bdd010c',
'org.whispersystems:curve25519-java:00f1d4919f759055f41f7853a3d475dc7c8decf0dbf045ae93414f8f23b066cc',
'com.android.support:support-media-compat:8d6a1a5ba3d9eb1a25cb8f21bb312ac6280202e3d2900cb0b447d065d0d8a125',
'com.android.support:support-core-utils:a7649e18c04143dde40c218c5ce9a030e7ae674089cd7b18c6cf8ed2a22cf01a',
'com.android.support:support-fragment:1294500b357f52cf3779e2521c79f54ae7844f3b9a5f6727495dbbda7f231377',

View File

@ -370,6 +370,10 @@
<string name="MessageDetailsRecipient_failed_to_send">Failed to send</string>
<string name="MessageDetailsRecipient_new_safety_number">New safety number</string>
<!-- MessageRetrievalService -->
<string name="MessageRetrievalService_signal">Signal</string>
<string name="MessageRetrievalService_background_connection_enabled">Background connection enabled</string>
<!-- MmsDownloader -->
<string name="MmsDownloader_error_storing_mms">Error storing MMS!</string>
<string name="MmsDownloader_error_connecting_to_mms_provider">Error connecting to MMS provider</string>
@ -501,17 +505,16 @@
<string name="RegistrationActivity_the_number_you_specified_s_is_invalid">The number you
specified (%s) is invalid.
</string>
<string name="RegistrationActivity_unsupported">Unsupported</string>
<string name="RegistrationActivity_sorry_this_device_is_not_supported_for_data_messaging">Sorry,
this device is not supported for data messaging. Devices running versions of Android older
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.
</string>
<string name="RegistrationActivity_we_will_now_verify_that_the_following_number_is_associated_with_your_device_s">
Double-check that this is your number! We\'re about to verify it with an SMS.
</string>
<string name="RegistrationActivity_continue">Continue</string>
<string name="RegistrationActivity_edit">Edit</string>
<string name="RegistrationActivity_missing_google_play_services">Missing Google Play Services</string>
<string name="RegistrationActivity_this_device_is_missing_google_play_services">This device is missing Google Play Services. You can still use Signal, but this configuration may result in reduced reliability or performance.\n\nIf you are not an advanced user, are not running an aftermarket Android ROM, or believe that you are seeing this in error, please contact support@whispersystems.org for help trouble shooting.</string>
<string name="RegistrationActivity_i_understand">I understand</string>
<string name="RegistrationActivity_play_services_error">Play Services Error</string>
<string name="RegistrationActivity_google_play_services_is_updating_or_unavailable">Google Play Services is updating or temporarily unavailable. Please try again.</string>
<!-- RegistrationProblemsActivity -->
<string name="RegistrationProblemsActivity_possible_problems">Possible problems</string>

View File

@ -54,6 +54,7 @@ import android.view.ViewGroup;
import org.thoughtcrime.securesms.ConversationListAdapter.ItemClickListener;
import org.thoughtcrime.securesms.components.recyclerview.DeleteItemAnimator;
import org.thoughtcrime.securesms.components.reminder.DefaultSmsReminder;
import org.thoughtcrime.securesms.components.reminder.DozeReminder;
import org.thoughtcrime.securesms.components.reminder.ExpiredBuildReminder;
import org.thoughtcrime.securesms.components.reminder.OutdatedBuildReminder;
import org.thoughtcrime.securesms.components.reminder.PushRegistrationReminder;
@ -184,6 +185,8 @@ public class ConversationListFragment extends Fragment
return Optional.of((new PushRegistrationReminder(context, masterSecret)));
} else if (ShareReminder.isEligible(context)) {
return Optional.of(new ShareReminder(context));
} else if (DozeReminder.isEligible(context)) {
return Optional.of(new DozeReminder(context));
} else {
return Optional.absent();
}

View File

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
@ -20,6 +21,7 @@ import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.i18n.phonenumbers.AsYouTypeFormatter;
import com.google.i18n.phonenumbers.NumberParseException;
@ -44,6 +46,13 @@ public class RegistrationActivity extends BaseActionBarActivity {
private static final int PICK_COUNTRY = 1;
private static final String TAG = RegistrationActivity.class.getSimpleName();
private enum PlayServicesStatus {
SUCCESS,
MISSING,
NEEDS_UPDATE,
TRANSIENT_ERROR
}
private AsYouTypeFormatter countryFormatter;
private ArrayAdapter<String> countrySpinnerAdapter;
private Spinner countrySpinner;
@ -211,28 +220,32 @@ public class RegistrationActivity extends BaseActionBarActivity {
return;
}
int gcmStatus = GooglePlayServicesUtil.isGooglePlayServicesAvailable(self);
PlayServicesStatus gcmStatus = checkPlayServices(self);
if (gcmStatus != ConnectionResult.SUCCESS) {
if (GooglePlayServicesUtil.isUserRecoverableError(gcmStatus)) {
GooglePlayServicesUtil.getErrorDialog(gcmStatus, self, 9000).show();
} else {
Dialogs.showAlertDialog(self, getString(R.string.RegistrationActivity_unsupported),
getString(R.string.RegistrationActivity_sorry_this_device_is_not_supported_for_data_messaging));
}
return;
if (gcmStatus == PlayServicesStatus.SUCCESS) {
promptForRegistrationStart(self, e164number, true);
} else if (gcmStatus == PlayServicesStatus.MISSING) {
promptForNoPlayServices(self, e164number);
} else if (gcmStatus == PlayServicesStatus.NEEDS_UPDATE) {
GoogleApiAvailability.getInstance().getErrorDialog(self, ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED, 0);
} else {
Dialogs.showAlertDialog(self, getString(R.string.RegistrationActivity_play_services_error),
getString(R.string.RegistrationActivity_google_play_services_is_updating_or_unavailable));
}
}
AlertDialog.Builder dialog = new AlertDialog.Builder(self);
private void promptForRegistrationStart(final Context context, final String e164number, final boolean gcmSupported) {
AlertDialog.Builder dialog = new AlertDialog.Builder(context);
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);
Intent intent = new Intent(context, RegistrationProgressActivity.class);
intent.putExtra(RegistrationProgressActivity.NUMBER_EXTRA, e164number);
intent.putExtra(RegistrationProgressActivity.MASTER_SECRET_EXTRA, masterSecret);
intent.putExtra(RegistrationProgressActivity.GCM_SUPPORTED_EXTRA, gcmSupported);
startActivity(intent);
finish();
}
@ -240,6 +253,48 @@ public class RegistrationActivity extends BaseActionBarActivity {
dialog.setNegativeButton(getString(R.string.RegistrationActivity_edit), null);
dialog.show();
}
private void promptForNoPlayServices(final Context context, final String e164number) {
AlertDialog.Builder dialog = new AlertDialog.Builder(context);
dialog.setTitle(R.string.RegistrationActivity_missing_google_play_services);
dialog.setMessage(R.string.RegistrationActivity_this_device_is_missing_google_play_services);
dialog.setPositiveButton(R.string.RegistrationActivity_i_understand, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
promptForRegistrationStart(context, e164number, false);
}
});
dialog.setNegativeButton(android.R.string.cancel, null);
dialog.show();
}
private PlayServicesStatus checkPlayServices(Context context) {
int gcmStatus = 0;
try {
gcmStatus = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context);
} catch (Throwable t) {
Log.w(TAG, t);
return PlayServicesStatus.MISSING;
}
Log.w(TAG, "Play Services: " + gcmStatus);
switch (gcmStatus) {
case ConnectionResult.SUCCESS:
return PlayServicesStatus.SUCCESS;
case ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED:
return PlayServicesStatus.NEEDS_UPDATE;
case ConnectionResult.SERVICE_DISABLED:
case ConnectionResult.SERVICE_MISSING:
case ConnectionResult.SERVICE_INVALID:
case ConnectionResult.API_UNAVAILABLE:
case ConnectionResult.SERVICE_MISSING_PERMISSION:
return PlayServicesStatus.MISSING;
default:
return PlayServicesStatus.TRANSIENT_ERROR;
}
}
}
private class CountryCodeChangedListener implements TextWatcher {

View File

@ -49,6 +49,10 @@ public class RegistrationProgressActivity extends BaseActionBarActivity {
private static final String TAG = RegistrationProgressActivity.class.getSimpleName();
public static final String NUMBER_EXTRA = "e164number";
public static final String MASTER_SECRET_EXTRA = "master_secret";
public static final String GCM_SUPPORTED_EXTRA = "gcm_supported";
private static final int FOCUSED_COLOR = Color.parseColor("#ff333333");
private static final int UNFOCUSED_COLOR = Color.parseColor("#ff808080");
@ -89,6 +93,7 @@ public class RegistrationProgressActivity extends BaseActionBarActivity {
private EditText codeEditText;
private MasterSecret masterSecret;
private boolean gcmSupported;
private volatile boolean visible;
@Override
@ -131,7 +136,8 @@ public class RegistrationProgressActivity extends BaseActionBarActivity {
}
private void initializeResources() {
this.masterSecret = getIntent().getParcelableExtra("master_secret");
this.masterSecret = getIntent().getParcelableExtra(MASTER_SECRET_EXTRA);
this.gcmSupported = getIntent().getBooleanExtra(GCM_SUPPORTED_EXTRA, true);
this.registrationLayout = (LinearLayout)findViewById(R.id.registering_layout);
this.verificationFailureLayout = (LinearLayout)findViewById(R.id.verification_failure_layout);
this.connectivityFailureLayout = (LinearLayout)findViewById(R.id.connectivity_failure_layout);
@ -199,8 +205,9 @@ public class RegistrationProgressActivity extends BaseActionBarActivity {
if (hasNumberDirective()) {
Intent intent = new Intent(this, RegistrationService.class);
intent.setAction(RegistrationService.REGISTER_NUMBER_ACTION);
intent.putExtra("e164number", getNumberDirective());
intent.putExtra("master_secret", masterSecret);
intent.putExtra(RegistrationService.NUMBER_EXTRA, getNumberDirective());
intent.putExtra(RegistrationService.MASTER_SECRET_EXTRA, masterSecret);
intent.putExtra(RegistrationService.GCM_SUPPORTED_EXTRA, gcmSupported);
startService(intent);
} else {
Intent intent = new Intent(this, RegistrationActivity.class);
@ -295,7 +302,7 @@ public class RegistrationProgressActivity extends BaseActionBarActivity {
private void handleVerificationRequestedVoice(RegistrationState state) {
handleVerificationTimeout(state);
verifyButton.setOnClickListener(new VerifyClickListener(state.number, state.password));
verifyButton.setOnClickListener(new VerifyClickListener(state.number, state.password, gcmSupported));
verifyButton.setEnabled(true);
codeEditText.setEnabled(true);
}
@ -354,11 +361,11 @@ public class RegistrationProgressActivity extends BaseActionBarActivity {
}
private boolean hasNumberDirective() {
return getIntent().getStringExtra("e164number") != null;
return getIntent().getStringExtra(NUMBER_EXTRA) != null;
}
private String getNumberDirective() {
return getIntent().getStringExtra("e164number");
return getIntent().getStringExtra(NUMBER_EXTRA);
}
private void shutdownServiceBinding() {
@ -426,7 +433,8 @@ public class RegistrationProgressActivity extends BaseActionBarActivity {
shutdownService();
Intent activityIntent = new Intent(RegistrationProgressActivity.this, RegistrationActivity.class);
activityIntent.putExtra("master_secret", masterSecret);
activityIntent.putExtra(RegistrationProgressActivity.MASTER_SECRET_EXTRA, masterSecret);
activityIntent.putExtra(RegistrationProgressActivity.GCM_SUPPORTED_EXTRA, gcmSupported);
startActivity(activityIntent);
finish();
}
@ -447,17 +455,19 @@ public class RegistrationProgressActivity extends BaseActionBarActivity {
private static final int VERIFICATION_ERROR = 3;
private static final int MULTI_REGISTRATION_ERROR = 4;
private final String e164number;
private final String password;
private final String signalingKey;
private final String e164number;
private final String password;
private final String signalingKey;
private final boolean gcmSupported;
private final Context context;
private ProgressDialog progressDialog;
public VerifyClickListener(String e164number, String password) {
public VerifyClickListener(String e164number, String password, boolean gcmSupported) {
this.e164number = e164number;
this.password = password;
this.signalingKey = Util.getSecret(52);
this.gcmSupported = gcmSupported;
this.context = RegistrationProgressActivity.this;
}
@ -490,10 +500,11 @@ public class RegistrationProgressActivity extends BaseActionBarActivity {
case SUCCESS:
Intent intent = new Intent(context, RegistrationService.class);
intent.setAction(RegistrationService.VOICE_REGISTER_ACTION);
intent.putExtra("e164number", e164number);
intent.putExtra("password", password);
intent.putExtra("signaling_key", signalingKey);
intent.putExtra("master_secret", masterSecret);
intent.putExtra(RegistrationService.NUMBER_EXTRA, e164number);
intent.putExtra(RegistrationService.PASSWORD_EXTRA, password);
intent.putExtra(RegistrationService.SIGNALING_KEY_EXTRA, signalingKey);
intent.putExtra(RegistrationService.MASTER_SECRET_EXTRA, masterSecret);
intent.putExtra(RegistrationService.GCM_SUPPORTED_EXTRA, gcmSupported);
startService(intent);
break;
case NETWORK_ERROR:
@ -522,7 +533,7 @@ public class RegistrationProgressActivity extends BaseActionBarActivity {
int registrationId = TextSecurePreferences.getLocalRegistrationId(context);
boolean video = TextSecurePreferences.isWebrtcCallingEnabled(context);
accountManager.verifyAccountWithCode(code, signalingKey, registrationId, true, video);
accountManager.verifyAccountWithCode(code, signalingKey, registrationId, true, video, !gcmSupported);
return SUCCESS;
} catch (ExpectationFailedException e) {
@ -578,9 +589,10 @@ public class RegistrationProgressActivity extends BaseActionBarActivity {
case SUCCESS:
Intent intent = new Intent(context, RegistrationService.class);
intent.setAction(RegistrationService.VOICE_REQUESTED_ACTION);
intent.putExtra("e164number", e164number);
intent.putExtra("password", password);
intent.putExtra("master_secret", masterSecret);
intent.putExtra(RegistrationService.NUMBER_EXTRA, e164number);
intent.putExtra(RegistrationService.PASSWORD_EXTRA, password);
intent.putExtra(RegistrationService.MASTER_SECRET_EXTRA, masterSecret);
intent.putExtra(RegistrationService.GCM_SUPPORTED_EXTRA, gcmSupported);
startService(intent);
callButton.setEnabled(false);

View File

@ -0,0 +1,48 @@
package org.thoughtcrime.securesms.components.reminder;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.PowerManager;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.view.View;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
public class DozeReminder extends Reminder {
@RequiresApi(api = Build.VERSION_CODES.M)
public DozeReminder(@NonNull final Context context) {
super("Optimize for missing Play Services",
"This device does not support Play Services. Tap to disable system battery optimizations that prevent Signal from retrieving messages while inactive.");
setOkListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TextSecurePreferences.setPromptedOptimizeDoze(context, true);
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
Uri.parse("package:" + context.getPackageName()));
context.startActivity(intent);
}
});
setDismissListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TextSecurePreferences.setPromptedOptimizeDoze(context, true);
}
});
}
public static boolean isEligible(Context context) {
return TextSecurePreferences.isGcmDisabled(context) &&
!TextSecurePreferences.hasPromptedOptimizeDoze(context) &&
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
!((PowerManager)context.getSystemService(Context.POWER_SERVICE)).isIgnoringBatteryOptimizations(context.getPackageName());
}
}

View File

@ -60,6 +60,8 @@ public class GcmRefreshJob extends ContextJob implements InjectableType {
@Override
public void onRun() throws Exception {
if (TextSecurePreferences.isGcmDisabled(context)) return;
String registrationId = TextSecurePreferences.getGcmRegistrationId(context);
if (registrationId == null) {

View File

@ -43,11 +43,12 @@ public class RefreshAttributesJob extends ContextJob implements InjectableType {
String gcmRegistrationId = TextSecurePreferences.getGcmRegistrationId(context);
int registrationId = TextSecurePreferences.getLocalRegistrationId(context);
boolean video = TextSecurePreferences.isWebrtcCallingEnabled(context);
boolean fetchesMessages = TextSecurePreferences.isGcmDisabled(context);
String token = signalAccountManager.getAccountVerificationToken();
redPhoneAccountManager.createAccount(token, new RedPhoneAccountAttributes(signalingKey, gcmRegistrationId));
signalAccountManager.setAccountAttributes(signalingKey, registrationId, true, video);
signalAccountManager.setAccountAttributes(signalingKey, registrationId, true, video || fetchesMessages, fetchesMessages);
}
@Override

View File

@ -58,9 +58,11 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.service.MessageRetrievalService;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.SpanUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.webrtc.CallNotificationManager;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import java.util.HashSet;
@ -155,7 +157,12 @@ public class MessageNotifier {
for (StatusBarNotification notification : activeNotifications) {
boolean validNotification = false;
if (notification.getId() != SUMMARY_NOTIFICATION_ID && notification.getId() != NotificationBarManager.RED_PHONE_NOTIFICATION) {
if (notification.getId() != SUMMARY_NOTIFICATION_ID &&
notification.getId() != NotificationBarManager.RED_PHONE_NOTIFICATION &&
notification.getId() != CallNotificationManager.WEBRTC_NOTIFICATION &&
notification.getId() != KeyCachingService.SERVICE_RUNNING_ID &&
notification.getId() != MessageRetrievalService.FOREGROUND_ID)
{
for (NotificationItem item : notificationState.getNotifications()) {
if (notification.getId() == (SUMMARY_NOTIFICATION_ID + item.getThreadId())) {
validNotification = true;

View File

@ -99,7 +99,9 @@ public class AdvancedPreferenceFragment extends PreferenceFragment {
}
private void initializeWebrtcCallingToggle() {
if (Build.VERSION.SDK_INT >= 11) {
if (TextSecurePreferences.isGcmDisabled(getContext())) {
getPreferenceScreen().removePreference(findPreference(TextSecurePreferences.WEBRTC_CALLING_PREF));
} else if (Build.VERSION.SDK_INT >= 11) {
this.findPreference(TextSecurePreferences.WEBRTC_CALLING_PREF)
.setOnPreferenceChangeListener(new WebRtcClickListener());
} else {

View File

@ -5,9 +5,11 @@ import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.gcm.GcmBroadcastReceiver;
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob;
@ -24,16 +26,20 @@ import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.inject.Inject;
public class MessageRetrievalService extends Service implements Runnable, InjectableType, RequirementListener {
public class MessageRetrievalService extends Service implements InjectableType, RequirementListener {
private static final String TAG = MessageRetrievalService.class.getSimpleName();
public static final String ACTION_ACTIVITY_STARTED = "ACTIVITY_STARTED";
public static final String ACTION_ACTIVITY_FINISHED = "ACTIVITY_FINISHED";
public static final String ACTION_PUSH_RECEIVED = "PUSH_RECEIVED";
public static final String ACTION_INITIALIZE = "INITIALIZE";
public static final int FOREGROUND_ID = 313399;
private static final long REQUEST_TIMEOUT_MINUTES = 1;
private NetworkRequirement networkRequirement;
@ -42,8 +48,9 @@ public class MessageRetrievalService extends Service implements Runnable, Inject
@Inject
public SignalServiceMessageReceiver receiver;
private int activeActivities = 0;
private List<Intent> pushPending = new LinkedList<>();
private int activeActivities = 0;
private List<Intent> pushPending = new LinkedList<>();
private MessageRetrievalThread retrievalThread = null;
public static SignalServiceMessagePipe pipe = null;
@ -56,7 +63,11 @@ public class MessageRetrievalService extends Service implements Runnable, Inject
networkRequirementProvider = new NetworkRequirementProvider(this);
networkRequirementProvider.setListener(this);
new Thread(this, "MessageRetrievalService").start();
retrievalThread = new MessageRetrievalThread();
retrievalThread.start();
setForegroundIfNecessary();
}
public int onStartCommand(Intent intent, int flags, int startId) {
@ -70,44 +81,11 @@ public class MessageRetrievalService extends Service implements Runnable, Inject
}
@Override
public void run() {
while (true) {
Log.w(TAG, "Waiting for websocket state change....");
waitForConnectionNecessary();
public void onDestroy() {
super.onDestroy();
Log.w(TAG, "Making websocket connection....");
pipe = receiver.createMessagePipe();
try {
while (isConnectionNecessary()) {
try {
Log.w(TAG, "Reading message...");
pipe.read(REQUEST_TIMEOUT_MINUTES, TimeUnit.MINUTES,
new SignalServiceMessagePipe.MessagePipeCallback() {
@Override
public void onMessage(SignalServiceEnvelope envelope) {
Log.w(TAG, "Retrieved envelope! " + envelope.getSource());
PushContentReceiveJob receiveJob = new PushContentReceiveJob(MessageRetrievalService.this);
receiveJob.handle(envelope, false);
decrementPushReceived();
}
});
} catch (TimeoutException e) {
Log.w(TAG, "Application level read timeout...");
} catch (InvalidVersionException e) {
Log.w(TAG, e);
}
}
} catch (Throwable e) {
Log.w(TAG, e);
} finally {
Log.w(TAG, "Shutting down pipe...");
shutdown(pipe);
}
Log.w(TAG, "Looping...");
if (retrievalThread != null) {
retrievalThread.stopThread();
}
}
@ -123,6 +101,18 @@ public class MessageRetrievalService extends Service implements Runnable, Inject
return null;
}
private void setForegroundIfNecessary() {
if (TextSecurePreferences.isGcmDisabled(this)) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setContentTitle(getString(R.string.MessageRetrievalService_signal));
builder.setContentText(getString(R.string.MessageRetrievalService_background_connection_enabled));
builder.setPriority(NotificationCompat.PRIORITY_MIN);
builder.setWhen(0);
builder.setSmallIcon(R.drawable.ic_signal_grey_24dp);
startForeground(FOREGROUND_ID, builder.build());
}
}
private synchronized void incrementActive() {
activeActivities++;
Log.w(TAG, "Active Count: " + activeActivities);
@ -149,11 +139,13 @@ public class MessageRetrievalService extends Service implements Runnable, Inject
}
private synchronized boolean isConnectionNecessary() {
Log.w(TAG, String.format("Network requirement: %s, active activities: %s, push pending: %s",
networkRequirement.isPresent(), activeActivities, pushPending.size()));
boolean isGcmDisabled = TextSecurePreferences.isGcmDisabled(this);
return TextSecurePreferences.isWebsocketRegistered(this) &&
(activeActivities > 0 || !pushPending.isEmpty()) &&
Log.w(TAG, String.format("Network requirement: %s, active activities: %s, push pending: %s, gcm disabled: %b",
networkRequirement.isPresent(), activeActivities, pushPending.size(), isGcmDisabled));
return TextSecurePreferences.isWebsocketRegistered(this) &&
(activeActivities > 0 || !pushPending.isEmpty() || isGcmDisabled) &&
networkRequirement.isPresent();
}
@ -188,4 +180,59 @@ public class MessageRetrievalService extends Service implements Runnable, Inject
public static @Nullable SignalServiceMessagePipe getPipe() {
return pipe;
}
private class MessageRetrievalThread extends Thread {
private AtomicBoolean stopThread = new AtomicBoolean(false);
@Override
public void run() {
while (!stopThread.get()) {
Log.w(TAG, "Waiting for websocket state change....");
waitForConnectionNecessary();
Log.w(TAG, "Making websocket connection....");
pipe = receiver.createMessagePipe();
SignalServiceMessagePipe localPipe = pipe;
try {
while (isConnectionNecessary() && !stopThread.get()) {
try {
Log.w(TAG, "Reading message...");
localPipe.read(REQUEST_TIMEOUT_MINUTES, TimeUnit.MINUTES,
new SignalServiceMessagePipe.MessagePipeCallback() {
@Override
public void onMessage(SignalServiceEnvelope envelope) {
Log.w(TAG, "Retrieved envelope! " + envelope.getSource());
PushContentReceiveJob receiveJob = new PushContentReceiveJob(MessageRetrievalService.this);
receiveJob.handle(envelope, false);
decrementPushReceived();
}
});
} catch (TimeoutException e) {
Log.w(TAG, "Application level read timeout...");
} catch (InvalidVersionException e) {
Log.w(TAG, e);
}
}
} catch (Throwable e) {
Log.w(TAG, e);
} finally {
Log.w(TAG, "Shutting down pipe...");
shutdown(localPipe);
}
Log.w(TAG, "Looping...");
}
Log.w(TAG, "Exiting...");
}
public void stopThread() {
stopThread.set(true);
}
}
}

View File

@ -0,0 +1,19 @@
package org.thoughtcrime.securesms.service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
public class PersistentConnectionBootListener extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (TextSecurePreferences.isGcmDisabled(context)) {
Intent serviceIntent = new Intent(context, MessageRetrievalService.class);
serviceIntent.setAction(MessageRetrievalService.ACTION_INITIALIZE);
context.startService(serviceIntent);
}
}
}

View File

@ -71,7 +71,12 @@ public class RegistrationService extends Service {
public static final String CHALLENGE_EVENT = "org.thoughtcrime.securesms.CHALLENGE_EVENT";
public static final String REGISTRATION_EVENT = "org.thoughtcrime.securesms.REGISTRATION_EVENT";
public static final String CHALLENGE_EXTRA = "CAAChallenge";
public static final String NUMBER_EXTRA = "e164number";
public static final String MASTER_SECRET_EXTRA = "master_secret";
public static final String GCM_SUPPORTED_EXTRA = "gcm_supported";
public static final String PASSWORD_EXTRA = "password";
public static final String SIGNALING_KEY_EXTRA = "signaling_key";
public static final String CHALLENGE_EXTRA = "CAAChallenge";
private static final long REGISTRATION_TIMEOUT_MILLIS = 120000;
@ -149,21 +154,22 @@ public class RegistrationService extends Service {
private void handleVoiceRequestedIntent(Intent intent) {
setState(new RegistrationState(RegistrationState.STATE_VOICE_REQUESTED,
intent.getStringExtra("e164number"),
intent.getStringExtra("password")));
intent.getStringExtra(NUMBER_EXTRA),
intent.getStringExtra(PASSWORD_EXTRA)));
}
private void handleVoiceRegistrationIntent(Intent intent) {
markAsVerifying(true);
String number = intent.getStringExtra("e164number");
String password = intent.getStringExtra("password");
String signalingKey = intent.getStringExtra("signaling_key");
String number = intent.getStringExtra(NUMBER_EXTRA);
String password = intent.getStringExtra(PASSWORD_EXTRA);
String signalingKey = intent.getStringExtra(SIGNALING_KEY_EXTRA);
boolean supportsGcm = intent.getBooleanExtra(GCM_SUPPORTED_EXTRA, true);
try {
SignalServiceAccountManager accountManager = AccountManagerFactory.createManager(this, number, password);
handleCommonRegistration(accountManager, number, password, signalingKey);
handleCommonRegistration(accountManager, number, password, signalingKey, supportsGcm);
markAsVerified(number, password, signalingKey);
@ -183,8 +189,10 @@ public class RegistrationService extends Service {
private void handleSmsRegistrationIntent(Intent intent) {
markAsVerifying(true);
String number = intent.getStringExtra("e164number");
int registrationId = TextSecurePreferences.getLocalRegistrationId(this);
String number = intent.getStringExtra(NUMBER_EXTRA);
boolean supportsGcm = intent.getBooleanExtra(GCM_SUPPORTED_EXTRA, true);
int registrationId = TextSecurePreferences.getLocalRegistrationId(this);
boolean supportsVideo = TextSecurePreferences.isWebrtcCallingEnabled(this) || !supportsGcm;
if (registrationId == 0) {
registrationId = KeyHelper.generateRegistrationId(false);
@ -203,9 +211,9 @@ public class RegistrationService extends Service {
setState(new RegistrationState(RegistrationState.STATE_VERIFYING, number));
String challenge = waitForChallenge();
accountManager.verifyAccountWithCode(challenge, signalingKey, registrationId, true, TextSecurePreferences.isWebrtcCallingEnabled(this));
accountManager.verifyAccountWithCode(challenge, signalingKey, registrationId, true, supportsVideo, !supportsGcm);
handleCommonRegistration(accountManager, number, password, signalingKey);
handleCommonRegistration(accountManager, number, password, signalingKey, supportsGcm);
markAsVerified(number, password, signalingKey);
setState(new RegistrationState(RegistrationState.STATE_COMPLETE, number));
@ -231,7 +239,7 @@ public class RegistrationService extends Service {
}
}
private void handleCommonRegistration(SignalServiceAccountManager accountManager, String number, String password, String signalingKey)
private void handleCommonRegistration(SignalServiceAccountManager accountManager, String number, String password, String signalingKey, boolean supportsGcm)
throws IOException
{
setState(new RegistrationState(RegistrationState.STATE_GENERATING_KEYS, number));
@ -244,21 +252,29 @@ public class RegistrationService extends Service {
setState(new RegistrationState(RegistrationState.STATE_GCM_REGISTERING, number));
String gcmRegistrationId = GoogleCloudMessaging.getInstance(this).register(GcmRefreshJob.REGISTRATION_ID);
accountManager.setGcmId(Optional.of(gcmRegistrationId));
if (supportsGcm) {
String gcmRegistrationId = GoogleCloudMessaging.getInstance(this).register(GcmRefreshJob.REGISTRATION_ID);
accountManager.setGcmId(Optional.of(gcmRegistrationId));
TextSecurePreferences.setGcmRegistrationId(this, gcmRegistrationId);
TextSecurePreferences.setGcmDisabled(this, false);
} else {
TextSecurePreferences.setGcmDisabled(this, true);
}
TextSecurePreferences.setGcmRegistrationId(this, gcmRegistrationId);
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);
if (supportsGcm) {
RedPhoneAccountManager redPhoneAccountManager = new RedPhoneAccountManager(BuildConfig.REDPHONE_MASTER_URL,
new RedPhoneTrustStore(this),
number, password);
String verificationToken = accountManager.getAccountVerificationToken();
redPhoneAccountManager.createAccount(verificationToken, new RedPhoneAccountAttributes(signalingKey, gcmRegistrationId));
String verificationToken = accountManager.getAccountVerificationToken();
redPhoneAccountManager.createAccount(verificationToken, new RedPhoneAccountAttributes(signalingKey, TextSecurePreferences.getGcmRegistrationId(this)));
}
DirectoryRefreshListener.schedule(this);
RotateSignedPreKeyListener.schedule(this);

View File

@ -69,6 +69,7 @@ public class TextSecurePreferences {
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";
private static final String PROMPTED_OPTIMIZE_DOZE_PREF = "pref_prompted_optimize_doze";
private static final String PROMPTED_SHARE_PREF = "pref_prompted_share";
private static final String SIGNALING_KEY_PREF = "pref_signaling_key";
private static final String DIRECTORY_FRESH_TIME_PREF = "pref_directory_refresh_time";
@ -80,6 +81,7 @@ public class TextSecurePreferences {
private static final String SIGNED_PREKEY_REGISTERED_PREF = "pref_signed_prekey_registered";
private static final String WIFI_SMS_PREF = "pref_wifi_sms";
private static final String GCM_DISABLED_PREF = "pref_gcm_disabled";
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";
private static final String WEBSOCKET_REGISTERED_PREF = "pref_websocket_registered";
@ -105,6 +107,14 @@ public class TextSecurePreferences {
return getBooleanPreference(context, ALWAYS_RELAY_CALLS_PREF, false);
}
public static boolean isGcmDisabled(Context context) {
return getBooleanPreference(context, GCM_DISABLED_PREF, false);
}
public static void setGcmDisabled(Context context, boolean disabled) {
setBooleanPreference(context, GCM_DISABLED_PREF, disabled);
}
public static boolean isWebrtcCallingEnabled(Context context) {
return getBooleanPreference(context, WEBRTC_CALLING_PREF, false);
}
@ -481,6 +491,14 @@ public class TextSecurePreferences {
setBooleanPreference(context, PROMPTED_DEFAULT_SMS_PREF, value);
}
public static void setPromptedOptimizeDoze(Context context, boolean value) {
setBooleanPreference(context, PROMPTED_OPTIMIZE_DOZE_PREF, value);
}
public static boolean hasPromptedOptimizeDoze(Context context) {
return getBooleanPreference(context, PROMPTED_OPTIMIZE_DOZE_PREF, false);
}
public static boolean hasPromptedShare(Context context) {
return getBooleanPreference(context, PROMPTED_SHARE_PREF, false);
}