diff --git a/AndroidManifest.xml b/AndroidManifest.xml index e50d780fc7..3c1369452a 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -524,13 +524,6 @@ android:exported="false" android:authorities="org.thoughtcrime.provider.securesms.mms" /> - - - - - - diff --git a/src/org/thoughtcrime/securesms/service/RegistrationNotifier.java b/src/org/thoughtcrime/securesms/service/RegistrationNotifier.java deleted file mode 100644 index 99bb5ad707..0000000000 --- a/src/org/thoughtcrime/securesms/service/RegistrationNotifier.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.thoughtcrime.securesms.service; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.support.v4.app.NotificationCompat; - -import org.thoughtcrime.securesms.ConversationListActivity; -import org.thoughtcrime.securesms.R; - -public class RegistrationNotifier extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - NotificationCompat.Builder builder = new NotificationCompat.Builder(context); - builder.setSmallIcon(R.drawable.icon_notification); - builder.setContentTitle(intent.getStringExtra(RegistrationService.NOTIFICATION_TITLE)); - builder.setContentText(intent.getStringExtra(RegistrationService.NOTIFICATION_TEXT)); - builder.setContentIntent(PendingIntent.getActivity(context, 0, new Intent(context, ConversationListActivity.class), 0)); - builder.setWhen(System.currentTimeMillis()); - builder.setDefaults(Notification.DEFAULT_VIBRATE); - builder.setAutoCancel(true); - - Notification notification = builder.build(); - ((NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE)).notify(31337, notification); - } -} diff --git a/src/org/thoughtcrime/securesms/service/RegistrationService.java b/src/org/thoughtcrime/securesms/service/RegistrationService.java deleted file mode 100644 index 6c8fa6223a..0000000000 --- a/src/org/thoughtcrime/securesms/service/RegistrationService.java +++ /dev/null @@ -1,387 +0,0 @@ -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.os.Binder; -import android.os.Handler; -import android.os.IBinder; -import android.util.Log; - -import com.google.android.gms.gcm.GoogleCloudMessaging; - -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; -import org.thoughtcrime.securesms.crypto.PreKeyUtil; -import org.thoughtcrime.securesms.crypto.SessionUtil; -import org.thoughtcrime.securesms.database.Address; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.IdentityDatabase; -import org.thoughtcrime.securesms.jobs.GcmRefreshJob; -import org.thoughtcrime.securesms.push.AccountManagerFactory; -import org.thoughtcrime.securesms.util.DirectoryHelper; -import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.thoughtcrime.securesms.util.Util; -import org.whispersystems.libsignal.IdentityKeyPair; -import org.whispersystems.libsignal.state.PreKeyRecord; -import org.whispersystems.libsignal.state.SignedPreKeyRecord; -import org.whispersystems.libsignal.util.KeyHelper; -import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.api.SignalServiceAccountManager; -import org.whispersystems.signalservice.api.push.exceptions.ExpectationFailedException; - -import java.io.IOException; -import java.lang.ref.WeakReference; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -/** - * The RegisterationService handles the process of PushService registration and verification. - * 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 REGISTER_NUMBER_ACTION = "org.thoughtcrime.securesms.RegistrationService.REGISTER_NUMBER"; - public static final String VOICE_REQUESTED_ACTION = "org.thoughtcrime.securesms.RegistrationService.VOICE_REQUESTED"; - public static final String VOICE_REGISTER_ACTION = "org.thoughtcrime.securesms.RegistrationService.VOICE_REGISTER"; - - 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 CHALLENGE_EVENT = "org.thoughtcrime.securesms.CHALLENGE_EVENT"; - public static final String REGISTRATION_EVENT = "org.thoughtcrime.securesms.REGISTRATION_EVENT"; - - 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; - - private final ExecutorService executor = Executors.newSingleThreadExecutor(); - private final Binder binder = new RegistrationServiceBinder(); - - private volatile RegistrationState registrationState = new RegistrationState(RegistrationState.STATE_IDLE); - - private volatile WeakReference registrationStateHandler; - private volatile ChallengeReceiver challengeReceiver; - private String challenge; - private long verificationStartTime; - private boolean generatingPreKeys; - - @Override - public int onStartCommand(final Intent intent, int flags, int startId) { - if (intent != null) { - executor.execute(new Runnable() { - @Override - public void run() { - if (REGISTER_NUMBER_ACTION.equals(intent.getAction())) handleSmsRegistrationIntent(intent); - else if (VOICE_REQUESTED_ACTION.equals(intent.getAction())) handleVoiceRequestedIntent(intent); - else if (VOICE_REGISTER_ACTION.equals(intent.getAction())) handleVoiceRegistrationIntent(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 synchronized void shutdownChallengeListener() { - if (challengeReceiver != null) { - unregisterReceiver(challengeReceiver); - challengeReceiver = null; - } - } - - private void handleVoiceRequestedIntent(Intent intent) { - setState(new RegistrationState(RegistrationState.STATE_VOICE_REQUESTED, - intent.getStringExtra(NUMBER_EXTRA), - intent.getStringExtra(PASSWORD_EXTRA))); - } - - private void handleVoiceRegistrationIntent(Intent intent) { - markAsVerifying(true); - - 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, supportsGcm); - - markAsVerified(number, password, signalingKey); - - 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 (IOException e) { - Log.w("RegistrationService", e); - setState(new RegistrationState(RegistrationState.STATE_NETWORK_ERROR, number)); - broadcastComplete(false); - } - } - - private void handleSmsRegistrationIntent(Intent intent) { - markAsVerifying(true); - - String number = intent.getStringExtra(NUMBER_EXTRA); - boolean supportsGcm = intent.getBooleanExtra(GCM_SUPPORTED_EXTRA, true); - int registrationId = KeyHelper.generateRegistrationId(false); - TextSecurePreferences.setLocalRegistrationId(this, registrationId); - SessionUtil.archiveAllSessions(this); - - try { - String password = Util.getSecret(18); - String signalingKey = Util.getSecret(52); - - initializeChallengeListener(); - - setState(new RegistrationState(RegistrationState.STATE_CONNECTING, number)); - SignalServiceAccountManager accountManager = AccountManagerFactory.createManager(this, number, password); - accountManager.requestSmsVerificationCode(); - - setState(new RegistrationState(RegistrationState.STATE_VERIFYING, number)); - String challenge = waitForChallenge(); - accountManager.verifyAccountWithCode(challenge, signalingKey, registrationId, !supportsGcm); - - TextSecurePreferences.setLocalNumber(this, number); - handleCommonRegistration(accountManager, number, password, signalingKey, supportsGcm); - markAsVerified(number, password, signalingKey); - - setState(new RegistrationState(RegistrationState.STATE_COMPLETE, number)); - broadcastComplete(true); - } catch (ExpectationFailedException efe) { - Log.w("RegistrationService", efe); - setState(new RegistrationState(RegistrationState.STATE_MULTI_REGISTERED, number)); - broadcastComplete(false); - } 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); - } finally { - shutdownChallengeListener(); - } - } - - private void handleCommonRegistration(SignalServiceAccountManager accountManager, String number, String password, String signalingKey, boolean supportsGcm) - throws IOException - { - setState(new RegistrationState(RegistrationState.STATE_GENERATING_KEYS, number)); - Address self = Address.fromSerialized(number); - IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(this); - List records = PreKeyUtil.generatePreKeys(this); - SignedPreKeyRecord signedPreKey = PreKeyUtil.generateSignedPreKey(this, identityKey, true); - accountManager.setPreKeys(identityKey.getPublicKey(), signedPreKey, records); - - setState(new RegistrationState(RegistrationState.STATE_GCM_REGISTERING, number)); - - 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.setWebsocketRegistered(this, true); - - DatabaseFactory.getIdentityDatabase(this).saveIdentity(self, identityKey.getPublicKey(), IdentityDatabase.VerifiedStatus.VERIFIED, true, System.currentTimeMillis(), true); - DirectoryHelper.refreshDirectory(this, accountManager); - - DirectoryRefreshListener.schedule(this); - RotateSignedPreKeyListener.schedule(this); - } - - 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 void challengeReceived(String challenge) { - this.challenge = challenge; - notifyAll(); - } - - private void markAsVerifying(boolean verifying) { - TextSecurePreferences.setVerifying(this, verifying); - - if (verifying) { - TextSecurePreferences.setPushRegistered(this, false); - } - } - - private void markAsVerified(String number, String password, String signalingKey) { - TextSecurePreferences.setVerifying(this, false); - TextSecurePreferences.setPushRegistered(this, true); - TextSecurePreferences.setLocalNumber(this, number); - TextSecurePreferences.setPushServerPassword(this, password); - TextSecurePreferences.setSignalingKey(this, signalingKey); - TextSecurePreferences.setSignedPreKeyRegistered(this, true); - TextSecurePreferences.setPromptedPushRegistration(this, true); - } - - private void setState(RegistrationState state) { - this.registrationState = state; - - Handler registrationStateHandler = this.registrationStateHandler.get(); - - if (registrationStateHandler != null) { - registrationStateHandler.obtainMessage(state.state, state).sendToTarget(); - } - } - - private void broadcastComplete(boolean success) { - Intent intent = new Intent(); - intent.setAction(REGISTRATION_EVENT); - - if (success) { - intent.putExtra(NOTIFICATION_TITLE, getString(R.string.RegistrationService_registration_complete)); - intent.putExtra(NOTIFICATION_TEXT, getString(R.string.RegistrationService_signal_registration_has_successfully_completed)); - } else { - intent.putExtra(NOTIFICATION_TITLE, getString(R.string.RegistrationService_registration_error)); - intent.putExtra(NOTIFICATION_TEXT, getString(R.string.RegistrationService_signal_registration_has_encountered_a_problem)); - } - - this.sendOrderedBroadcast(intent, null); - } - - public void setRegistrationStateHandler(Handler registrationStateHandler) { - this.registrationStateHandler = new WeakReference<>(registrationStateHandler); - } - - public class RegistrationServiceBinder extends Binder { - public RegistrationService getService() { - return RegistrationService.this; - } - } - - 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 static final int STATE_VOICE_REQUESTED = 12; - public static final int STATE_GENERATING_KEYS = 13; - - public static final int STATE_MULTI_REGISTERED = 14; - - public final int state; - public final String number; - public final String password; - - public RegistrationState(int state) { - this(state, null); - } - - public RegistrationState(int state, String number) { - this(state, number, null); - } - - public RegistrationState(int state, String number, String password) { - this.state = state; - this.number = number; - this.password = password; - } - } -} diff --git a/src/org/thoughtcrime/securesms/service/SmsListener.java b/src/org/thoughtcrime/securesms/service/SmsListener.java index c9ac0b532b..b2535f282e 100644 --- a/src/org/thoughtcrime/securesms/service/SmsListener.java +++ b/src/org/thoughtcrime/securesms/service/SmsListener.java @@ -29,6 +29,7 @@ import android.telephony.SmsMessage; import android.util.Log; import org.thoughtcrime.securesms.ApplicationContext; +import org.thoughtcrime.securesms.RegistrationActivity; import org.thoughtcrime.securesms.jobs.SmsReceiveJob; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; @@ -146,8 +147,8 @@ public class SmsListener extends BroadcastReceiver { String messageBody = getSmsMessageBodyFromIntent(intent); if (SMS_RECEIVED_ACTION.equals(intent.getAction()) && isChallenge(context, messageBody)) { Log.w("SmsListener", "Got challenge!"); - Intent challengeIntent = new Intent(RegistrationService.CHALLENGE_EVENT); - challengeIntent.putExtra(RegistrationService.CHALLENGE_EXTRA, parseChallenge(messageBody)); + Intent challengeIntent = new Intent(RegistrationActivity.CHALLENGE_EVENT); + challengeIntent.putExtra(RegistrationActivity.CHALLENGE_EXTRA, parseChallenge(messageBody)); context.sendBroadcast(challengeIntent); abortBroadcast();