mirror of
https://github.com/oxen-io/session-android.git
synced 2025-10-20 14:54:09 +00:00
Initial GCM registration
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
package org.thoughtcrime.securesms.service;
|
||||
|
||||
public class AccountVerificationTimeoutException extends Exception {
|
||||
public AccountVerificationTimeoutException() {
|
||||
}
|
||||
|
||||
public AccountVerificationTimeoutException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
public AccountVerificationTimeoutException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
}
|
||||
|
||||
public AccountVerificationTimeoutException(Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
}
|
330
src/org/thoughtcrime/securesms/service/RegistrationService.java
Normal file
330
src/org/thoughtcrime/securesms/service/RegistrationService.java
Normal file
@@ -0,0 +1,330 @@
|
||||
package org.thoughtcrime.securesms.service;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gcm.GCMRegistrar;
|
||||
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||||
import org.thoughtcrime.securesms.gcm.GcmIntentService;
|
||||
import org.thoughtcrime.securesms.gcm.GcmRegistrationTimeoutException;
|
||||
import org.thoughtcrime.securesms.gcm.RegistrationSocket;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* The RegisterationService handles the actual process of registration. If it receives an
|
||||
* intent with a REGISTER_NUMBER_ACTION, it does the following through an executor:
|
||||
*
|
||||
* 1) Generate secrets.
|
||||
* 2) Register the specified number and those secrets with the server.
|
||||
* 3) Wait for a challenge SMS.
|
||||
* 4) Verify the challenge with the server.
|
||||
* 5) Start the GCM registration process.
|
||||
* 6) Retrieve the current directory.
|
||||
*
|
||||
* The RegistrationService broadcasts its state throughout this process, and also makes its
|
||||
* state available through service binding. This enables a View to display progress.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class RegistrationService extends Service {
|
||||
|
||||
public static final String NOTIFICATION_TITLE = "org.thoughtcrime.securesms.NOTIFICATION_TITLE";
|
||||
public static final String NOTIFICATION_TEXT = "org.thoughtcrime.securesms.NOTIFICATION_TEXT";
|
||||
public static final String REGISTER_NUMBER_ACTION = "org.thoughtcrime.securesms.RegistrationService.REGISTER_NUMBER";
|
||||
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 GCM_REGISTRATION_EVENT = "org.thoughtcrime.securesms.GCM_REGISTRATION_EVENT";
|
||||
|
||||
public static final String CHALLENGE_EXTRA = "CAAChallenge";
|
||||
public static final String GCM_REGISTRATION_ID = "GCMRegistrationId";
|
||||
|
||||
private static final long REGISTRATION_TIMEOUT_MILLIS = 120000;
|
||||
|
||||
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
private final Binder binder = new RegistrationServiceBinder();
|
||||
|
||||
private volatile RegistrationState registrationState = new RegistrationState(RegistrationState.STATE_IDLE);
|
||||
|
||||
private volatile Handler registrationStateHandler;
|
||||
private volatile ChallengeReceiver challengeReceiver;
|
||||
private volatile GcmRegistrationReceiver gcmRegistrationReceiver;
|
||||
private String challenge;
|
||||
private String gcmRegistrationId;
|
||||
private long verificationStartTime;
|
||||
|
||||
@Override
|
||||
public int onStartCommand(final Intent intent, int flags, int startId) {
|
||||
if (intent != null && intent.getAction().equals(REGISTER_NUMBER_ACTION)) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handleRegistrationIntent(intent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
executor.shutdown();
|
||||
shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return binder;
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
shutdownChallengeListener();
|
||||
markAsVerifying(false);
|
||||
registrationState = new RegistrationState(RegistrationState.STATE_IDLE);
|
||||
}
|
||||
|
||||
public synchronized int getSecondsRemaining() {
|
||||
long millisPassed;
|
||||
|
||||
if (verificationStartTime == 0) millisPassed = 0;
|
||||
else millisPassed = System.currentTimeMillis() - verificationStartTime;
|
||||
|
||||
return Math.max((int)(REGISTRATION_TIMEOUT_MILLIS - millisPassed) / 1000, 0);
|
||||
}
|
||||
|
||||
public RegistrationState getRegistrationState() {
|
||||
return registrationState;
|
||||
}
|
||||
|
||||
private void initializeChallengeListener() {
|
||||
this.challenge = null;
|
||||
challengeReceiver = new ChallengeReceiver();
|
||||
IntentFilter filter = new IntentFilter(CHALLENGE_EVENT);
|
||||
registerReceiver(challengeReceiver, filter);
|
||||
}
|
||||
|
||||
private void initializeGcmRegistrationListener() {
|
||||
this.gcmRegistrationId = null;
|
||||
gcmRegistrationReceiver = new GcmRegistrationReceiver();
|
||||
IntentFilter filter = new IntentFilter(GCM_REGISTRATION_EVENT);
|
||||
registerReceiver(gcmRegistrationReceiver, filter);
|
||||
}
|
||||
|
||||
private void shutdownChallengeListener() {
|
||||
if (challengeReceiver != null) {
|
||||
unregisterReceiver(challengeReceiver);
|
||||
challengeReceiver = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void shutdownGcmRegistrationListener() {
|
||||
if (gcmRegistrationReceiver != null) {
|
||||
unregisterReceiver(gcmRegistrationReceiver);
|
||||
gcmRegistrationReceiver = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRegistrationIntent(Intent intent) {
|
||||
markAsVerifying(true);
|
||||
|
||||
RegistrationSocket socket;
|
||||
String number = intent.getStringExtra("e164number");
|
||||
|
||||
try {
|
||||
String password = Util.getSecret(18);
|
||||
initializeChallengeListener();
|
||||
initializeGcmRegistrationListener();
|
||||
|
||||
setState(new RegistrationState(RegistrationState.STATE_CONNECTING, number));
|
||||
socket = new RegistrationSocket(this, number, password);
|
||||
socket.createAccount();
|
||||
|
||||
setState(new RegistrationState(RegistrationState.STATE_VERIFYING, number));
|
||||
String challenge = waitForChallenge();
|
||||
socket.verifyAccount(challenge, password);
|
||||
|
||||
setState(new RegistrationState(RegistrationState.STATE_GCM_REGISTERING, number));
|
||||
GCMRegistrar.register(this, GcmIntentService.GCM_SENDER_ID);
|
||||
String gcmRegistrationId = waitForGcmRegistrationId();
|
||||
socket.registerGcmId(gcmRegistrationId);
|
||||
|
||||
markAsVerified(number, password);
|
||||
|
||||
setState(new RegistrationState(RegistrationState.STATE_COMPLETE, number));
|
||||
broadcastComplete(true);
|
||||
} catch (UnsupportedOperationException uoe) {
|
||||
Log.w("RegistrationService", uoe);
|
||||
setState(new RegistrationState(RegistrationState.STATE_GCM_UNSUPPORTED, number));
|
||||
broadcastComplete(false);
|
||||
} catch (AccountVerificationTimeoutException avte) {
|
||||
Log.w("RegistrationService", avte);
|
||||
setState(new RegistrationState(RegistrationState.STATE_TIMEOUT, number));
|
||||
broadcastComplete(false);
|
||||
} catch (IOException e) {
|
||||
Log.w("RegistrationService", e);
|
||||
setState(new RegistrationState(RegistrationState.STATE_NETWORK_ERROR, number));
|
||||
broadcastComplete(false);
|
||||
} catch (GcmRegistrationTimeoutException e) {
|
||||
Log.w("RegistrationService", e);
|
||||
setState(new RegistrationState(RegistrationState.STATE_GCM_TIMEOUT));
|
||||
broadcastComplete(false);
|
||||
} finally {
|
||||
shutdownChallengeListener();
|
||||
shutdownGcmRegistrationListener();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized String waitForChallenge() throws AccountVerificationTimeoutException {
|
||||
this.verificationStartTime = System.currentTimeMillis();
|
||||
|
||||
if (this.challenge == null) {
|
||||
try {
|
||||
wait(REGISTRATION_TIMEOUT_MILLIS);
|
||||
} catch (InterruptedException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.challenge == null)
|
||||
throw new AccountVerificationTimeoutException();
|
||||
|
||||
return this.challenge;
|
||||
}
|
||||
|
||||
private synchronized String waitForGcmRegistrationId() throws GcmRegistrationTimeoutException {
|
||||
if (this.gcmRegistrationId == null) {
|
||||
try {
|
||||
wait(10 * 60 * 1000);
|
||||
} catch (InterruptedException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.gcmRegistrationId == null)
|
||||
throw new GcmRegistrationTimeoutException();
|
||||
|
||||
return this.gcmRegistrationId;
|
||||
}
|
||||
|
||||
private synchronized void challengeReceived(String challenge) {
|
||||
this.challenge = challenge;
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
private synchronized void gcmRegistrationReceived(String gcmRegistrationId) {
|
||||
this.gcmRegistrationId = gcmRegistrationId;
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
private void markAsVerifying(boolean verifying) {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
Editor editor = preferences.edit();
|
||||
|
||||
editor.putBoolean(ApplicationPreferencesActivity.VERIFYING_STATE_PREF, verifying);
|
||||
editor.putBoolean(ApplicationPreferencesActivity.REGISTERED_GCM_PREF, false);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
private void markAsVerified(String number, String password) {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
Editor editor = preferences.edit();
|
||||
|
||||
editor.putBoolean(ApplicationPreferencesActivity.VERIFYING_STATE_PREF, false);
|
||||
editor.putBoolean(ApplicationPreferencesActivity.REGISTERED_GCM_PREF, true);
|
||||
editor.putString(ApplicationPreferencesActivity.LOCAL_NUMBER_PREF, number);
|
||||
editor.putString(ApplicationPreferencesActivity.GCM_PASSWORD_PREF, password);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
private void setState(RegistrationState state) {
|
||||
this.registrationState = state;
|
||||
|
||||
if (registrationStateHandler != null) {
|
||||
registrationStateHandler.obtainMessage(state.state, state.number).sendToTarget();
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcastComplete(boolean success) {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(REGISTRATION_EVENT);
|
||||
|
||||
if (success) {
|
||||
intent.putExtra(NOTIFICATION_TITLE, "Registration Complete");
|
||||
intent.putExtra(NOTIFICATION_TEXT, "TextSecure registration has successfully completed.");
|
||||
} else {
|
||||
intent.putExtra(NOTIFICATION_TITLE, "Registration Error");
|
||||
intent.putExtra(NOTIFICATION_TEXT, "TextSecure registration has encountered a problem.");
|
||||
}
|
||||
|
||||
this.sendOrderedBroadcast(intent, null);
|
||||
}
|
||||
|
||||
public void setRegistrationStateHandler(Handler registrationStateHandler) {
|
||||
this.registrationStateHandler = registrationStateHandler;
|
||||
}
|
||||
|
||||
public class RegistrationServiceBinder extends Binder {
|
||||
public RegistrationService getService() {
|
||||
return RegistrationService.this;
|
||||
}
|
||||
}
|
||||
|
||||
private class GcmRegistrationReceiver extends BroadcastReceiver {
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.w("RegistrationService", "Got gcm registration broadcast...");
|
||||
gcmRegistrationReceived(intent.getStringExtra(GCM_REGISTRATION_ID));
|
||||
}
|
||||
}
|
||||
|
||||
private class ChallengeReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.w("RegistrationService", "Got a challenge broadcast...");
|
||||
challengeReceived(intent.getStringExtra(CHALLENGE_EXTRA));
|
||||
}
|
||||
}
|
||||
|
||||
public static class RegistrationState {
|
||||
|
||||
public static final int STATE_IDLE = 0;
|
||||
public static final int STATE_CONNECTING = 1;
|
||||
public static final int STATE_VERIFYING = 2;
|
||||
public static final int STATE_TIMER = 3;
|
||||
public static final int STATE_COMPLETE = 4;
|
||||
public static final int STATE_TIMEOUT = 5;
|
||||
public static final int STATE_NETWORK_ERROR = 6;
|
||||
|
||||
public static final int STATE_GCM_UNSUPPORTED = 8;
|
||||
public static final int STATE_GCM_REGISTERING = 9;
|
||||
public static final int STATE_GCM_TIMEOUT = 10;
|
||||
|
||||
public final int state;
|
||||
public final String number;
|
||||
|
||||
public RegistrationState(int state) {
|
||||
this(state, null);
|
||||
}
|
||||
|
||||
public RegistrationState(int state, String number) {
|
||||
this.state = state;
|
||||
this.number = number;
|
||||
}
|
||||
}
|
||||
}
|
@@ -19,11 +19,13 @@ package org.thoughtcrime.securesms.service;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.telephony.SmsMessage;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||||
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
||||
|
||||
public class SmsListener extends BroadcastReceiver {
|
||||
@@ -91,11 +93,44 @@ public class SmsListener extends BroadcastReceiver {
|
||||
return WirePrefix.isEncryptedMessage(messageBody) || WirePrefix.isKeyExchange(messageBody);
|
||||
}
|
||||
|
||||
private boolean isChallenge(Context context, Intent intent) {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
String messageBody = getSmsMessageBodyFromIntent(intent);
|
||||
|
||||
Log.w("SmsListener", "Checking challenge: " + messageBody);
|
||||
|
||||
if (messageBody == null)
|
||||
return false;
|
||||
|
||||
if (messageBody.matches("Your TextSecure verification code: [0-9]{3,4}-[0-9]{3,4}") &&
|
||||
preferences.getBoolean(ApplicationPreferencesActivity.VERIFYING_STATE_PREF, false))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private String parseChallenge(Context context, Intent intent) {
|
||||
String messageBody = getSmsMessageBodyFromIntent(intent);
|
||||
String[] messageParts = messageBody.split(":");
|
||||
String[] codeParts = messageParts[1].trim().split("-");
|
||||
|
||||
return codeParts[0] + codeParts[1];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.w("SMSListener", "Got SMS broadcast...");
|
||||
|
||||
if (intent.getAction().equals(SMS_RECEIVED_ACTION) && isRelevant(context, intent)) {
|
||||
if (intent.getAction().equals(SMS_RECEIVED_ACTION) && isChallenge(context, intent)) {
|
||||
Log.w("SmsListener", "Got challenge!");
|
||||
Intent challengeIntent = new Intent(RegistrationService.CHALLENGE_EVENT);
|
||||
challengeIntent.putExtra(RegistrationService.CHALLENGE_EXTRA, parseChallenge(context, intent));
|
||||
context.sendBroadcast(challengeIntent);
|
||||
|
||||
abortBroadcast();
|
||||
} else if (intent.getAction().equals(SMS_RECEIVED_ACTION) && isRelevant(context, intent)) {
|
||||
intent.setAction(SendReceiveService.RECEIVE_SMS_ACTION);
|
||||
intent.putExtra("ResultCode", this.getResultCode());
|
||||
intent.setClass(context, SendReceiveService.class);
|
||||
|
Reference in New Issue
Block a user