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

@@ -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);
}