diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index b11ae1c641..8bdd09350b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -35,10 +35,9 @@
android:label="@string/app_name"
android:theme="@style/Theme.Sherlock.Light.DarkActionBar">
-
@@ -62,22 +61,34 @@
+
+
+
+
diff --git a/res/drawable-hdpi/import_database.png b/res/drawable-hdpi/import_database.png
new file mode 100644
index 0000000000..c59a8ac065
Binary files /dev/null and b/res/drawable-hdpi/import_database.png differ
diff --git a/res/drawable-hdpi/padlock_prompt.png b/res/drawable-hdpi/padlock_prompt.png
new file mode 100644
index 0000000000..d6d1980cc6
Binary files /dev/null and b/res/drawable-hdpi/padlock_prompt.png differ
diff --git a/res/drawable-mdpi/import_database.png b/res/drawable-mdpi/import_database.png
new file mode 100644
index 0000000000..b1c565b1bb
Binary files /dev/null and b/res/drawable-mdpi/import_database.png differ
diff --git a/res/drawable-mdpi/padlock_prompt.png b/res/drawable-mdpi/padlock_prompt.png
new file mode 100644
index 0000000000..7b4fc222fd
Binary files /dev/null and b/res/drawable-mdpi/padlock_prompt.png differ
diff --git a/res/drawable-xhdpi/import_database.png b/res/drawable-xhdpi/import_database.png
new file mode 100644
index 0000000000..a977ebb912
Binary files /dev/null and b/res/drawable-xhdpi/import_database.png differ
diff --git a/res/drawable-xhdpi/padlock_prompt.png b/res/drawable-xhdpi/padlock_prompt.png
new file mode 100644
index 0000000000..671f2e5c32
Binary files /dev/null and b/res/drawable-xhdpi/padlock_prompt.png differ
diff --git a/res/drawable/background_pattern.png b/res/drawable/background_pattern.png
new file mode 100644
index 0000000000..ac627053e8
Binary files /dev/null and b/res/drawable/background_pattern.png differ
diff --git a/res/drawable/background_pattern_repeat.xml b/res/drawable/background_pattern_repeat.xml
new file mode 100644
index 0000000000..5e3165a49f
--- /dev/null
+++ b/res/drawable/background_pattern_repeat.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/res/layout/change_passphrase_activity.xml b/res/layout/change_passphrase_activity.xml
index 0b901dbd3a..4f2b9bf007 100644
--- a/res/layout/change_passphrase_activity.xml
+++ b/res/layout/change_passphrase_activity.xml
@@ -1,90 +1,81 @@
+
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:fillViewport="true"
+ android:background="@drawable/background_pattern_repeat">
-
+ android:layout_height="fill_parent"
+ android:gravity="center" >
+
+
+
+
+
+
-
+
+
+
-
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
+ android:textAppearance="?android:attr/textAppearanceMedium"/>
+
-
-
+
\ No newline at end of file
diff --git a/res/layout/create_passphrase_activity.xml b/res/layout/create_passphrase_activity.xml
index 88cd30ba56..1288737b35 100644
--- a/res/layout/create_passphrase_activity.xml
+++ b/res/layout/create_passphrase_activity.xml
@@ -1,74 +1,87 @@
-
+ android:fillViewport="true"
+ android:background="@drawable/background_pattern_repeat">
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ android:layout_marginTop="16dip"
+ android:text="@string/create_passphrase_activity__please_choose_a_passphrase_that_will_be_used_to_locally_encrypt_your_data_this_should_be_a_strong_passphrase"/>
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
-
+
+
+
+
+
+
+
+
diff --git a/res/layout/database_migration_activity.xml b/res/layout/database_migration_activity.xml
new file mode 100644
index 0000000000..22a8752cae
--- /dev/null
+++ b/res/layout/database_migration_activity.xml
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/prompt_passphrase_activity.xml b/res/layout/prompt_passphrase_activity.xml
index 018ffb10f1..3c829b3529 100644
--- a/res/layout/prompt_passphrase_activity.xml
+++ b/res/layout/prompt_passphrase_activity.xml
@@ -1,39 +1,58 @@
-
-
+
-
-
-
-
-
-
-
-
-
-
+ android:text="@string/prompt_passphrase_activity__unlock"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="right"
+ android:textAppearance="?android:attr/textAppearanceMedium"/>
+
+
+
+
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f14b07f60b..2abd6556aa 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -118,9 +118,8 @@
Incorrect old passphrase!
- Passphrases Don\'t Match!
- Generating KeyPair
- Generating a local encryption keypair...
+ Passphrases don\'t match
+ You must specify a password
Invalid Passphrase!
@@ -225,8 +224,9 @@
Currently unable to send your MMS message. It will be sent once service becomes available.
- Migrating
- Migrating System Text Messages
+ Import in progress
+ Importing Text Messages
+ Import complete!
TextSecure Passphrase Cached
@@ -251,9 +251,9 @@
Initiate Exchange
- Old passphrase:
- New passphrase:
- Repeat new passphrase:
+ OLD PASSPHRASE:
+ NEW PASSPHRASE:
+ REPEAT NEW PASSPHRASE:
@@ -282,9 +282,23 @@
Batch Selection Mode
- Please choose a passphrase that will be used to locally encrypt your data. This should be a strong passphrase.
- Passphrase:
- Repeat:
+ Please choose a passphrase that will be used to locally encrypt your data.\n\nThis should be a strong passphrase.
+ PASSPHRASE:
+ REPEAT:
+ Continue
+ GENERATING SECRETS
+
+
+ Would you like to import your existing text messages into TextSecure\\\'s encrypted database?
+ The default system database will not be modified or altered in any way.
+ Skip
+ Import
+ This could take a moment. Please be patient, we\'ll notify you when the import is complete.
+ IMPORTING
+
+
+ TEXTSECURE PASSPHRASE
+ Unlock
Session
diff --git a/res/values/styles.xml b/res/values/styles.xml
index f313fcceee..b1ca82fe31 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -1,5 +1,9 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/org/thoughtcrime/securesms/ApplicationMigrationManager.java b/src/org/thoughtcrime/securesms/ApplicationMigrationManager.java
deleted file mode 100644
index d64a8f93b0..0000000000
--- a/src/org/thoughtcrime/securesms/ApplicationMigrationManager.java
+++ /dev/null
@@ -1,121 +0,0 @@
-package org.thoughtcrime.securesms;
-
-import android.app.AlertDialog;
-import android.app.ProgressDialog;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-
-import org.thoughtcrime.securesms.crypto.MasterSecret;
-import org.thoughtcrime.securesms.service.ApplicationMigrationService;
-
-public class ApplicationMigrationManager extends Handler {
-
- private ProgressDialog progressDialog;
- private ApplicationMigrationListener listener;
-
- private final Context context;
- private final MasterSecret masterSecret;
-
- public ApplicationMigrationManager(Context context,
- MasterSecret masterSecret)
- {
- this.masterSecret = masterSecret;
- this.context = context;
- }
-
- public void setMigrationListener(ApplicationMigrationListener listener) {
- this.listener = listener;
- }
-
- private void displayMigrationProgress() {
- progressDialog = new ProgressDialog(context);
- progressDialog.setTitle(context.getString(R.string.ApplicationMigrationManager_migrating_database));
- progressDialog.setMessage(context.getString(R.string.ApplicationMigrationManager_migrating_text_message_database));
- progressDialog.setMax(10000);
- progressDialog.setCancelable(false);
- progressDialog.setIndeterminate(false);
- progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
- progressDialog.show();
- }
-
- public void migrate() {
- context.bindService(new Intent(context, ApplicationMigrationService.class),
- serviceConnection, Context.BIND_AUTO_CREATE);
- }
-
- private void displayMigrationPrompt() {
- AlertDialog.Builder alertBuilder = new AlertDialog.Builder(context);
- alertBuilder.setTitle(R.string.ApplicationMigrationManager_copy_system_text_message_database_question);
- alertBuilder.setMessage(R.string.ApplicationMigrationManager_copy_system_text_message_database_explanation);
- alertBuilder.setCancelable(false);
-
- alertBuilder.setPositiveButton(R.string.ApplicationMigrationManager_copy,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- displayMigrationProgress();
- Intent intent = new Intent(context, ApplicationMigrationService.class);
- intent.setAction(ApplicationMigrationService.MIGRATE_DATABASE);
- intent.putExtra("master_secret", masterSecret);
- context.startService(intent);
- }
- });
-
- alertBuilder.setNegativeButton(R.string.ApplicationMigrationManager_dont_copy,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- context.getSharedPreferences("SecureSMS", Context.MODE_PRIVATE)
- .edit()
- .putBoolean("migrated", true).commit();
- listener.applicationMigrationComplete();
- }
- });
-
- alertBuilder.create().show();
- }
-
- @Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case ApplicationMigrationService.PROGRESS_UPDATE:
- if (progressDialog != null) {
- progressDialog.setProgress(message.arg1);
- progressDialog.setSecondaryProgress(message.arg2);
- }
- break;
- case ApplicationMigrationService.PROGRESS_COMPLETE:
- if (progressDialog != null) {
- progressDialog.dismiss();
- }
-
- if (listener != null) {
- listener.applicationMigrationComplete();
- }
- break;
- }
- }
-
- public static interface ApplicationMigrationListener {
- public void applicationMigrationComplete();
- }
-
- private ServiceConnection serviceConnection = new ServiceConnection() {
- public void onServiceConnected(ComponentName className, IBinder service) {
- ApplicationMigrationService applicationMigrationService
- = ((ApplicationMigrationService.ApplicationMigrationBinder)service).getService();
-
- if (applicationMigrationService.isMigrating()) displayMigrationProgress();
- else displayMigrationPrompt();
-
- applicationMigrationService.setHandler(ApplicationMigrationManager.this);
- }
-
- public void onServiceDisconnected(ComponentName name) {}
- };
-
-}
diff --git a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java
index ec0e1de785..2b224043d3 100644
--- a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java
+++ b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java
@@ -35,7 +35,7 @@ import org.thoughtcrime.securesms.contacts.ContactIdentityManager;
import org.thoughtcrime.securesms.crypto.IdentityKey;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.MasterSecret;
-import org.thoughtcrime.securesms.service.KeyCachingService;
+import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.MemoryCleaner;
import org.thoughtcrime.securesms.util.Trimmer;
@@ -138,7 +138,12 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
- case android.R.id.home: finish(); return true;
+ case android.R.id.home:
+ Intent intent = new Intent(this, ConversationListActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ finish();
+ return true;
}
return false;
@@ -313,9 +318,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
private class ChangePassphraseClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
- SharedPreferences settings = getSharedPreferences(KeyCachingService.PREFERENCES_NAME, 0);
-
- if (settings.getBoolean("passphrase_initialized", false)) {
+ if (MasterSecretUtil.isPassphraseInitialized(ApplicationPreferencesActivity.this)) {
startActivity(new Intent(ApplicationPreferencesActivity.this, PassphraseChangeActivity.class));
} else {
Toast.makeText(ApplicationPreferencesActivity.this,
diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java
index eb5404e4a3..94b4e84587 100644
--- a/src/org/thoughtcrime/securesms/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationActivity.java
@@ -236,7 +236,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
case R.id.menu_verify_recipient: handleVerifyRecipient(); return true;
case R.id.menu_verify_session: handleVerifySession(); return true;
case R.id.menu_group_recipients: handleDisplayGroupRecipients(); return true;
- case android.R.id.home: finish(); return true;
+ case android.R.id.home: handleReturnToConversationList(); return true;
}
return false;
@@ -261,6 +261,14 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
//////// Event Handlers
+ private void handleReturnToConversationList() {
+ Intent intent = new Intent(this, ConversationListActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ intent.putExtra("master_secret", masterSecret);
+ startActivity(intent);
+ finish();
+ }
+
private void handleVerifyRecipient() {
Intent verifyIdentityIntent = new Intent(this, VerifyIdentityActivity.class);
verifyIdentityIntent.putExtra("recipient", getRecipients().getPrimaryRecipient());
diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java
index 3f98b8d979..3f145ee8f4 100644
--- a/src/org/thoughtcrime/securesms/ConversationListActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java
@@ -1,25 +1,16 @@
package org.thoughtcrime.securesms;
-import android.content.Context;
import android.content.Intent;
-import android.content.SharedPreferences;
import android.database.ContentObserver;
-import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.os.Parcelable;
import android.provider.ContactsContract;
import android.util.Log;
import android.view.WindowManager;
import org.thoughtcrime.securesms.ApplicationExportManager.ApplicationExportListener;
-import org.thoughtcrime.securesms.crypto.DecryptingQueue;
-import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.MasterSecret;
-import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
-import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
-import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.service.SendReceiveService;
@@ -36,11 +27,6 @@ public class ConversationListActivity extends PassphraseRequiredSherlockFragment
private ConversationListFragment fragment;
private MasterSecret masterSecret;
- private ApplicationMigrationManager migrationManager;
-
- private boolean havePromptedForPassphrase = false;
- private boolean isVisible = false;
-
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -52,32 +38,6 @@ public class ConversationListActivity extends PassphraseRequiredSherlockFragment
initializeContactUpdatesReceiver();
}
- @Override
- protected void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
- this.setIntent(intent);
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- isVisible = true;
- }
-
- @Override
- public void onPause() {
- super.onPause();
-
- isVisible = false;
- }
-
- @Override
- public void onStop() {
- super.onStop();
- havePromptedForPassphrase = false;
- }
-
@Override
public void onDestroy() {
Log.w("ConversationListActivity", "onDestroy...");
@@ -87,47 +47,17 @@ public class ConversationListActivity extends PassphraseRequiredSherlockFragment
@Override
public void onMasterSecretCleared() {
- this.masterSecret = null;
- this.fragment.setMasterSecret(null);
- this.invalidateOptionsMenu();
-
- if (!havePromptedForPassphrase && isVisible) {
- promptForPassphrase();
- }
+// this.fragment.setMasterSecret(null);
+ startActivity(new Intent(this, RoutingActivity.class));
+ super.onMasterSecretCleared();
}
- @Override
- public void onNewMasterSecret(MasterSecret masterSecret) {
- this.masterSecret = masterSecret;
-
- if (masterSecret != null) {
- if (!IdentityKeyUtil.hasIdentityKey(this)) {
- new Thread(new IdentityKeyInitializer()).start();
- }
-
- if (!MasterSecretUtil.hasAsymmericMasterSecret(this)) {
- new Thread(new AsymmetricMasteSecretInitializer()).start();
- }
-
- if (!isDatabaseMigrated()) initializeDatabaseMigration();
- else DecryptingQueue.schedulePendingDecrypts(this, masterSecret);
- }
-
- this.fragment.setMasterSecret(masterSecret);
- this.invalidateOptionsMenu();
- this.havePromptedForPassphrase = false;
- createConversationIfNecessary(this.getIntent());
- }
-
-
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
- Log.w("ConversationListActivity", "onPrepareOptionsMenu...");
MenuInflater inflater = this.getSupportMenuInflater();
menu.clear();
- if (this.masterSecret == null) inflater.inflate(R.menu.text_secure_locked, menu);
- else inflater.inflate(R.menu.text_secure_normal, menu);
+ inflater.inflate(R.menu.text_secure_normal, menu);
super.onPrepareOptionsMenu(menu);
return true;
@@ -138,12 +68,11 @@ public class ConversationListActivity extends PassphraseRequiredSherlockFragment
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
- case R.id.menu_new_message: createConversation(-1, null, null, null, null); return true;
- case R.id.menu_unlock: promptForPassphrase(); return true;
- case R.id.menu_settings: handleDisplaySettings(); return true;
- case R.id.menu_export: handleExportDatabase(); return true;
- case R.id.menu_import: handleImportDatabase(); return true;
- case R.id.menu_clear_passphrase: handleClearPassphrase(); return true;
+ case R.id.menu_new_message: createConversation(-1, null); return true;
+ case R.id.menu_settings: handleDisplaySettings(); return true;
+ case R.id.menu_export: handleExportDatabase(); return true;
+ case R.id.menu_import: handleImportDatabase(); return true;
+ case R.id.menu_clear_passphrase: handleClearPassphrase(); return true;
}
return false;
@@ -151,39 +80,18 @@ public class ConversationListActivity extends PassphraseRequiredSherlockFragment
@Override
public void onCreateConversation(long threadId, Recipients recipients) {
- createConversation(threadId, recipients, null, null, null);
+ createConversation(threadId, recipients);
}
- private void createConversation(long threadId, Recipients recipients,
- String text, Uri imageUri, Uri audioUri)
- {
- if (this.masterSecret == null) {
- promptForPassphrase();
- return;
- }
-
+ private void createConversation(long threadId, Recipients recipients) {
Intent intent = new Intent(this, ConversationActivity.class);
intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, recipients);
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId);
intent.putExtra(ConversationActivity.MASTER_SECRET_EXTRA, masterSecret);
- intent.putExtra(ConversationActivity.DRAFT_TEXT_EXTRA, text);
- intent.putExtra(ConversationActivity.DRAFT_IMAGE_EXTRA, imageUri);
- intent.putExtra(ConversationActivity.DRAFT_AUDIO_EXTRA, audioUri);
startActivity(intent);
}
- private void promptForPassphrase() {
- havePromptedForPassphrase = true;
- if (hasSelectedPassphrase()) startActivity(new Intent(this, PassphrasePromptActivity.class));
- else startActivity(new Intent(this, PassphraseCreateActivity.class));
- }
-
- private boolean hasSelectedPassphrase() {
- SharedPreferences settings = getSharedPreferences(KeyCachingService.PREFERENCES_NAME, 0);
- return settings.getBoolean("passphrase_initialized", false);
- }
-
private void handleDisplaySettings() {
Intent preferencesIntent = new Intent(this, ApplicationPreferencesActivity.class);
preferencesIntent.putExtra("master_secret", masterSecret);
@@ -237,89 +145,17 @@ public class ConversationListActivity extends PassphraseRequiredSherlockFragment
startService(mmsSenderIntent);
}
- private void initializeDatabaseMigration() {
- if (migrationManager == null) {
- migrationManager = new ApplicationMigrationManager(this, masterSecret);
-
- ApplicationMigrationManager.ApplicationMigrationListener listener =
- new ApplicationMigrationManager.ApplicationMigrationListener() {
- @Override
- public void applicationMigrationComplete() {
- if (masterSecret != null)
- DecryptingQueue.schedulePendingDecrypts(ConversationListActivity.this,
- masterSecret);
- }
- };
-
- migrationManager.setMigrationListener(listener);
- migrationManager.migrate();
- }
- }
-
private void initializeResources() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE);
}
+ this.masterSecret = (MasterSecret)getIntent().getParcelableExtra("master_secret");
+
this.fragment = (ConversationListFragment)this.getSupportFragmentManager()
.findFragmentById(R.id.fragment_content);
- }
- private boolean isDatabaseMigrated() {
- return this.getSharedPreferences("SecureSMS", Context.MODE_PRIVATE)
- .getBoolean("migrated", false);
- }
-
- private void createConversationIfNecessary(Intent intent) {
- long thread = intent.getLongExtra("thread_id", -1L);
- String type = intent.getType();
- Recipients recipients = null;
- String draftText = null;
- Uri draftImage = null;
- Uri draftAudio = null;
-
- if (Intent.ACTION_SENDTO.equals(intent.getAction())) {
- try {
- recipients = RecipientFactory.getRecipientsFromString(this, intent.getData().getSchemeSpecificPart(), false);
- thread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipients);
- } catch (RecipientFormattingException rfe) {
- recipients = null;
- }
- } else if (Intent.ACTION_SEND.equals(intent.getAction())) {
- if ("text/plain".equals(type)) {
- draftText = intent.getStringExtra(Intent.EXTRA_TEXT);
- } else if (type.startsWith("image/")) {
- draftImage = intent.getParcelableExtra(Intent.EXTRA_STREAM);
- } else if (type.startsWith("audio/")) {
- draftAudio = intent.getParcelableExtra(Intent.EXTRA_STREAM);
- }
- } else {
- recipients = intent.getParcelableExtra("recipients");
- }
-
- if (recipients != null || Intent.ACTION_SEND.equals(intent.getAction())) {
- createConversation(thread, recipients, draftText, draftImage, draftAudio);
-
- intent.putExtra("thread_id", -1L);
- intent.putExtra("recipients", (Parcelable)null);
- intent.putExtra(Intent.EXTRA_TEXT, (String)null);
- intent.putExtra(Intent.EXTRA_STREAM, (Parcelable)null);
- intent.setAction(null);
- }
- }
-
- private class IdentityKeyInitializer implements Runnable {
- @Override
- public void run() {
- IdentityKeyUtil.generateIdentityKeys(ConversationListActivity.this, masterSecret);
- }
- }
-
- private class AsymmetricMasteSecretInitializer implements Runnable {
- @Override
- public void run() {
- MasterSecretUtil.generateAsymmetricMasterSecret(ConversationListActivity.this, masterSecret);
- }
+ this.fragment.setMasterSecret(masterSecret);
}
}
diff --git a/src/org/thoughtcrime/securesms/DatabaseMigrationActivity.java b/src/org/thoughtcrime/securesms/DatabaseMigrationActivity.java
new file mode 100644
index 0000000000..ce58f57e1e
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/DatabaseMigrationActivity.java
@@ -0,0 +1,189 @@
+package org.thoughtcrime.securesms;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import org.thoughtcrime.securesms.database.SmsMigrator.ProgressDescription;
+import org.thoughtcrime.securesms.service.ApplicationMigrationService;
+import org.thoughtcrime.securesms.service.ApplicationMigrationService.ImportState;
+
+public class DatabaseMigrationActivity extends PassphraseRequiredSherlockActivity {
+
+ private final ImportServiceConnection serviceConnection = new ImportServiceConnection();
+ private final ImportStateHandler importStateHandler = new ImportStateHandler();
+ private final BroadcastReceiver completedReceiver = new NullReceiver();
+
+ private LinearLayout promptLayout;
+ private LinearLayout progressLayout;
+ private Button skipButton;
+ private Button importButton;
+ private ProgressBar progress;
+ private TextView progressLabel;
+
+ private ApplicationMigrationService importService;
+ private boolean isVisible = false;
+
+ @Override
+ public void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ setContentView(R.layout.database_migration_activity);
+
+ initializeResources();
+ initializeServiceBinding();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ isVisible = true;
+ registerForCompletedNotification();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ isVisible = false;
+ unregisterForCompletedNotification();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ shutdownServiceBinding();
+ }
+
+ private void initializeServiceBinding() {
+ Intent intent = new Intent(this, ApplicationMigrationService.class);
+ bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ private void initializeResources() {
+ this.promptLayout = (LinearLayout)findViewById(R.id.prompt_layout);
+ this.progressLayout = (LinearLayout)findViewById(R.id.progress_layout);
+ this.skipButton = (Button) findViewById(R.id.skip_button);
+ this.importButton = (Button) findViewById(R.id.import_button);
+ this.progress = (ProgressBar) findViewById(R.id.import_progress);
+ this.progressLabel = (TextView) findViewById(R.id.import_status);
+
+ this.progressLayout.setVisibility(View.GONE);
+ this.promptLayout.setVisibility(View.GONE);
+
+ this.importButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(DatabaseMigrationActivity.this, ApplicationMigrationService.class);
+ intent.setAction(ApplicationMigrationService.MIGRATE_DATABASE);
+ intent.putExtra("master_secret", getIntent().getParcelableExtra("master_secret"));
+ startService(intent);
+
+ promptLayout.setVisibility(View.GONE);
+ progressLayout.setVisibility(View.VISIBLE);
+ }
+ });
+
+ this.skipButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ApplicationMigrationService.setDatabaseImported(DatabaseMigrationActivity.this);
+ handleImportComplete();
+ }
+ });
+ }
+
+ private void registerForCompletedNotification() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ApplicationMigrationService.COMPLETED_ACTION);
+ filter.setPriority(1000);
+
+ registerReceiver(completedReceiver, filter);
+ }
+
+ private void unregisterForCompletedNotification() {
+ unregisterReceiver(completedReceiver);
+ }
+
+ private void shutdownServiceBinding() {
+ unbindService(serviceConnection);
+ }
+
+ private void handleStateIdle() {
+ this.promptLayout.setVisibility(View.VISIBLE);
+ this.progressLayout.setVisibility(View.GONE);
+ }
+
+ private void handleStateProgress(ProgressDescription update) {
+ this.promptLayout.setVisibility(View.GONE);
+ this.progressLayout.setVisibility(View.VISIBLE);
+ this.progressLabel.setText(update.primaryComplete + "/" + update.primaryTotal);
+
+ double max = this.progress.getMax();
+ double primaryTotal = update.primaryTotal;
+ double primaryComplete = update.primaryComplete;
+ double secondaryTotal = update.secondaryTotal;
+ double secondaryComplete = update.secondaryComplete;
+
+ this.progress.setProgress((int)Math.round((primaryComplete / primaryTotal) * max));
+ this.progress.setSecondaryProgress((int)Math.round((secondaryComplete / secondaryTotal) * max));
+ }
+
+ private void handleImportComplete() {
+ if (isVisible) {
+ if (getIntent().hasExtra("next_intent")) {
+ startActivity((Intent)getIntent().getParcelableExtra("next_intent"));
+ } else {
+ startActivity(new Intent(this, ConversationListActivity.class));
+ }
+ }
+
+ finish();
+ }
+
+ private class ImportStateHandler extends Handler {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case ImportState.STATE_IDLE: handleStateIdle(); break;
+ case ImportState.STATE_MIGRATING_IN_PROGRESS: handleStateProgress((ProgressDescription)message.obj); break;
+ case ImportState.STATE_MIGRATING_COMPLETE: handleImportComplete(); break;
+ }
+ }
+ }
+
+ private class ImportServiceConnection implements ServiceConnection {
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ importService = ((ApplicationMigrationService.ApplicationMigrationBinder)service).getService();
+ importService.setImportStateHandler(importStateHandler);
+
+ ImportState state = importService.getState();
+ importStateHandler.obtainMessage(state.state, state.progress).sendToTarget();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ importService.setImportStateHandler(null);
+ }
+ }
+
+ private class NullReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ abortBroadcast();
+ }
+ }
+
+
+}
diff --git a/src/org/thoughtcrime/securesms/PassphraseActivity.java b/src/org/thoughtcrime/securesms/PassphraseActivity.java
index 03b979b34a..c3ffa72285 100644
--- a/src/org/thoughtcrime/securesms/PassphraseActivity.java
+++ b/src/org/thoughtcrime/securesms/PassphraseActivity.java
@@ -22,12 +22,12 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
-import com.actionbarsherlock.app.SherlockActivity;
-
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.MemoryCleaner;
+import com.actionbarsherlock.app.SherlockActivity;
+
/**
* Base Activity for changing/prompting local encryption passphrase.
*
@@ -44,9 +44,14 @@ public abstract class PassphraseActivity extends SherlockActivity {
bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}
+ protected MasterSecret getMasterSecret() {
+ return masterSecret;
+ }
+
protected abstract void cleanup();
private ServiceConnection serviceConnection = new ServiceConnection() {
+ @Override
public void onServiceConnected(ComponentName className, IBinder service) {
keyCachingService = ((KeyCachingService.KeyCachingBinder)service).getService();
keyCachingService.setMasterSecret(masterSecret);
@@ -55,9 +60,12 @@ public abstract class PassphraseActivity extends SherlockActivity {
MemoryCleaner.clean(masterSecret);
cleanup();
+
+ PassphraseActivity.this.setResult(RESULT_OK);
PassphraseActivity.this.finish();
}
+ @Override
public void onServiceDisconnected(ComponentName name) {
keyCachingService = null;
}
diff --git a/src/org/thoughtcrime/securesms/PassphraseCreateActivity.java b/src/org/thoughtcrime/securesms/PassphraseCreateActivity.java
index 37d818def6..e86573bad1 100644
--- a/src/org/thoughtcrime/securesms/PassphraseCreateActivity.java
+++ b/src/org/thoughtcrime/securesms/PassphraseCreateActivity.java
@@ -16,18 +16,19 @@
*/
package org.thoughtcrime.securesms;
-import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
+import android.widget.LinearLayout;
import android.widget.Toast;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.util.MemoryCleaner;
+import org.thoughtcrime.securesms.util.Util;
/**
* Activity for creating a user's local encryption passphrase.
@@ -37,10 +38,12 @@ import org.thoughtcrime.securesms.util.MemoryCleaner;
public class PassphraseCreateActivity extends PassphraseActivity {
+ private LinearLayout createLayout;
+ private LinearLayout progressLayout;
+
private EditText passphraseEdit;
private EditText passphraseRepeatEdit;
private Button okButton;
- private Button cancelButton;
public PassphraseCreateActivity() { }
@@ -54,10 +57,11 @@ public class PassphraseCreateActivity extends PassphraseActivity {
}
private void initializeResources() {
- this.passphraseEdit = (EditText) findViewById(R.id.passphrase_edit);
- this.passphraseRepeatEdit = (EditText) findViewById(R.id.passphrase_edit_repeat);
- this.okButton = (Button) findViewById(R.id.ok_button);
- this.cancelButton = (Button) findViewById(R.id.cancel_button);
+ this.createLayout = (LinearLayout)findViewById(R.id.create_layout);
+ this.progressLayout = (LinearLayout)findViewById(R.id.progress_layout);
+ this.passphraseEdit = (EditText) findViewById(R.id.passphrase_edit);
+ this.passphraseRepeatEdit = (EditText) findViewById(R.id.passphrase_edit_repeat);
+ this.okButton = (Button) findViewById(R.id.ok_button);
this.okButton.setOnClickListener(new View.OnClickListener() {
@Override
@@ -65,45 +69,36 @@ public class PassphraseCreateActivity extends PassphraseActivity {
verifyAndSavePassphrases();
}
});
-
- this.cancelButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- finish();
- }
- });
}
private void verifyAndSavePassphrases() {
+ if (Util.isEmpty(this.passphraseEdit) || Util.isEmpty(this.passphraseRepeatEdit)) {
+ Toast.makeText(this, R.string.PassphraseCreateActivity_you_must_specify_a_password, Toast.LENGTH_SHORT).show();
+ return;
+ }
+
String passphrase = this.passphraseEdit.getText().toString();
String passphraseRepeat = this.passphraseRepeatEdit.getText().toString();
if (!passphrase.equals(passphraseRepeat)) {
- Toast.makeText(getApplicationContext(),
- R.string.PassphraseCreateActivity_passphrases_dont_match_exclamation,
- Toast.LENGTH_SHORT).show();
+ Toast.makeText(this, R.string.PassphraseCreateActivity_passphrases_dont_match, Toast.LENGTH_SHORT).show();
this.passphraseEdit.setText("");
this.passphraseRepeatEdit.setText("");
- } else {
- // We do this, but the edit boxes are basically impossible to clean up.
- MemoryCleaner.clean(passphraseRepeat);
- new SecretGenerator().execute(passphrase);
+ return;
}
+
+ // We do this, but the edit boxes are basically impossible to clean up.
+ MemoryCleaner.clean(passphraseRepeat);
+ new SecretGenerator().execute(passphrase);
}
private class SecretGenerator extends AsyncTask {
- private ProgressDialog progressDialog;
private MasterSecret masterSecret;
@Override
protected void onPreExecute() {
- progressDialog = new ProgressDialog(PassphraseCreateActivity.this);
- progressDialog.setTitle(R.string.PassphraseCreateActivity_generating_keypair);
- progressDialog.setMessage(getString(R.string.PassphraseCreateActivity_generating_a_local_encryption_keypair));
- progressDialog.setCancelable(false);
- progressDialog.setIndeterminate(true);
- progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
- progressDialog.show();
+ createLayout.setVisibility(View.GONE);
+ progressLayout.setVisibility(View.VISIBLE);
}
@Override
@@ -123,7 +118,6 @@ public class PassphraseCreateActivity extends PassphraseActivity {
@Override
protected void onPostExecute(Void param) {
- progressDialog.dismiss();
setMasterSecret(masterSecret);
}
}
diff --git a/src/org/thoughtcrime/securesms/PassphrasePromptActivity.java b/src/org/thoughtcrime/securesms/PassphrasePromptActivity.java
index 714a045ba8..d8e40e9067 100644
--- a/src/org/thoughtcrime/securesms/PassphrasePromptActivity.java
+++ b/src/org/thoughtcrime/securesms/PassphrasePromptActivity.java
@@ -38,7 +38,6 @@ public class PassphrasePromptActivity extends PassphraseActivity {
private EditText passphraseText;
private Button okButton;
- private Button cancelButton;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -51,13 +50,12 @@ public class PassphrasePromptActivity extends PassphraseActivity {
private void initializeResources() {
passphraseText = (EditText)findViewById(R.id.passphrase_edit);
okButton = (Button)findViewById(R.id.ok_button);
- cancelButton = (Button)findViewById(R.id.cancel_button);
okButton.setOnClickListener(new OkButtonClickListener());
- cancelButton.setOnClickListener(new CancelButtonClickListener());
}
private class OkButtonClickListener implements OnClickListener {
+ @Override
public void onClick(View v) {
try {
Editable text = passphraseText.getText();
@@ -74,16 +72,9 @@ public class PassphrasePromptActivity extends PassphraseActivity {
}
}
- private class CancelButtonClickListener implements OnClickListener {
- public void onClick(View v) {
- finish();
- }
- }
-
@Override
protected void cleanup() {
this.passphraseText = null;
System.gc();
}
-
}
diff --git a/src/org/thoughtcrime/securesms/RoutingActivity.java b/src/org/thoughtcrime/securesms/RoutingActivity.java
new file mode 100644
index 0000000000..dedfaea7e5
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/RoutingActivity.java
@@ -0,0 +1,218 @@
+package org.thoughtcrime.securesms;
+
+import android.content.Intent;
+import android.net.Uri;
+
+import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
+import org.thoughtcrime.securesms.database.DatabaseFactory;
+import org.thoughtcrime.securesms.recipients.RecipientFactory;
+import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
+import org.thoughtcrime.securesms.recipients.Recipients;
+import org.thoughtcrime.securesms.service.ApplicationMigrationService;
+
+public class RoutingActivity extends PassphraseRequiredSherlockActivity {
+
+ private static final int STATE_CREATE_PASSPHRASE = 1;
+ private static final int STATE_PROMPT_PASSPHRASE = 2;
+ private static final int STATE_IMPORT_DATABASE = 3;
+ private static final int STATE_CONVERSATION_OR_LIST = 4;
+
+ private MasterSecret masterSecret = null;
+ private boolean isVisible = false;
+
+ @Override
+ public void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ setIntent(intent);
+ }
+
+ @Override
+ public void onResume() {
+ this.isVisible = true;
+ super.onResume();
+ }
+
+ @Override
+ public void onPause() {
+ this.isVisible = false;
+ super.onPause();
+ }
+
+ @Override
+ public void onNewMasterSecret(MasterSecret masterSecret) {
+ this.masterSecret = masterSecret;
+
+ if (isVisible) {
+ routeApplicationState();
+ }
+ }
+
+ @Override
+ public void onMasterSecretCleared() {
+ this.masterSecret = null;
+
+ if (isVisible) {
+ routeApplicationState();
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (resultCode == RESULT_CANCELED)
+ finish();
+ }
+
+ private void routeApplicationState() {
+ int state = getApplicationState();
+
+ switch (state) {
+ case STATE_CREATE_PASSPHRASE: handleCreatePassphrase(); break;
+ case STATE_PROMPT_PASSPHRASE: handlePromptPassphrase(); break;
+ case STATE_IMPORT_DATABASE: handleImportDatabase(); break;
+ case STATE_CONVERSATION_OR_LIST: handleDisplayConversationOrList(); break;
+ }
+ }
+
+ private void handleCreatePassphrase() {
+ Intent intent = new Intent(this, PassphraseCreateActivity.class);
+ startActivityForResult(intent, 1);
+ }
+
+ private void handlePromptPassphrase() {
+ Intent intent = new Intent(this, PassphrasePromptActivity.class);
+ startActivityForResult(intent, 2);
+ }
+
+ private void handleImportDatabase() {
+ Intent intent = new Intent(this, DatabaseMigrationActivity.class);
+ intent.putExtra("master_secret", masterSecret);
+ intent.putExtra("next_intent", getConversationListIntent());
+
+ startActivity(intent);
+ finish();
+ }
+
+ private void handleDisplayConversationOrList() {
+ ConversationParameters parameters = getConversationParameters();
+
+ Intent intent;
+
+ if (isShareAction() || parameters.recipients != null) {
+ intent = getConversationIntent(parameters);
+ } else {
+ intent = getConversationListIntent();
+ }
+
+ startActivity(intent);
+ finish();
+ }
+
+ private Intent getConversationIntent(ConversationParameters parameters) {
+ Intent intent = new Intent(this, ConversationActivity.class);
+ intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, parameters.recipients);
+ intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, parameters.thread);
+ intent.putExtra(ConversationActivity.MASTER_SECRET_EXTRA, masterSecret);
+ intent.putExtra(ConversationActivity.DRAFT_TEXT_EXTRA, parameters.draftText);
+ intent.putExtra(ConversationActivity.DRAFT_IMAGE_EXTRA, parameters.draftImage);
+ intent.putExtra(ConversationActivity.DRAFT_AUDIO_EXTRA, parameters.draftAudio);
+
+ return intent;
+ }
+
+ private Intent getConversationListIntent() {
+ Intent intent = new Intent(this, ConversationListActivity.class);
+ intent.putExtra("master_secret", masterSecret);
+
+ return intent;
+ }
+
+ private int getApplicationState() {
+ if (!MasterSecretUtil.isPassphraseInitialized(this))
+ return STATE_CREATE_PASSPHRASE;
+
+ if (masterSecret == null)
+ return STATE_PROMPT_PASSPHRASE;
+
+ if (!ApplicationMigrationService.isDatabaseImported(this))
+ return STATE_IMPORT_DATABASE;
+
+ return STATE_CONVERSATION_OR_LIST;
+ }
+
+ private ConversationParameters getConversationParameters() {
+ if (isSendAction()) {
+ return getConversationParametersForSendAction();
+ } else if (isShareAction()) {
+ return getConversationParametersForShareAction();
+ } else {
+ return getConversationParametersForInternalAction();
+ }
+ }
+
+ private ConversationParameters getConversationParametersForSendAction() {
+ Recipients recipients = null;
+ long threadId = getIntent().getLongExtra("thread_id", -1);
+
+ try {
+ String data = getIntent().getData().getSchemeSpecificPart();
+ recipients = RecipientFactory.getRecipientsFromString(this, data, false);
+ threadId = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipients);
+ } catch (RecipientFormattingException rfe) {
+ recipients = null;
+ }
+
+ return new ConversationParameters(threadId, recipients, null, null, null);
+ }
+
+ private ConversationParameters getConversationParametersForShareAction() {
+ String type = getIntent().getType();
+ String draftText = null;
+ Uri draftImage = null;
+ Uri draftAudio = null;
+
+ if ("text/plain".equals(type)) {
+ draftText = getIntent().getStringExtra(Intent.EXTRA_TEXT);
+ } else if (type.startsWith("image/")) {
+ draftImage = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
+ } else if (type.startsWith("audio/")) {
+ draftAudio = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
+ }
+
+ return new ConversationParameters(-1, null, draftText, draftImage, draftAudio);
+ }
+
+ private ConversationParameters getConversationParametersForInternalAction() {
+ long threadId = getIntent().getLongExtra("thread_id", -1);
+ Recipients recipients = getIntent().getParcelableExtra("recipients");
+
+ return new ConversationParameters(threadId, recipients, null, null, null);
+ }
+
+ private boolean isShareAction() {
+ return Intent.ACTION_SEND.equals(getIntent().getAction());
+ }
+
+ private boolean isSendAction() {
+ return Intent.ACTION_SENDTO.equals(getIntent().getAction());
+ }
+
+ private static class ConversationParameters {
+ public final long thread;
+ public final Recipients recipients;
+ public final String draftText;
+ public final Uri draftImage;
+ public final Uri draftAudio;
+
+ public ConversationParameters(long thread, Recipients recipients,
+ String draftText, Uri draftImage, Uri draftAudio)
+ {
+ this.thread = thread;
+ this.recipients = recipients;
+ this.draftText = draftText;
+ this.draftImage = draftImage;
+ this.draftAudio = draftAudio;
+ }
+ }
+
+}
diff --git a/src/org/thoughtcrime/securesms/crypto/MasterSecretUtil.java b/src/org/thoughtcrime/securesms/crypto/MasterSecretUtil.java
index 4718ad1111..4b84e7ef2e 100644
--- a/src/org/thoughtcrime/securesms/crypto/MasterSecretUtil.java
+++ b/src/org/thoughtcrime/securesms/crypto/MasterSecretUtil.java
@@ -1,6 +1,6 @@
-/**
+/**
* Copyright (C) 2011 Whisper Systems
- *
+ *
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -10,12 +10,21 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.thoughtcrime.securesms.crypto;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.util.Log;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.thoughtcrime.securesms.util.Base64;
+
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
@@ -31,38 +40,29 @@ import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import javax.crypto.spec.SecretKeySpec;
-import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
-import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
-import org.thoughtcrime.securesms.util.Base64;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-import android.util.Log;
-
/**
* Helper class for generating and securely storing a MasterSecret.
- *
+ *
* @author Moxie Marlinspike
*/
public class MasterSecretUtil {
-
+
public static final String PREFERENCES_NAME = "SecureSMS-Preferences";
public static final String ASYMMETRIC_LOCAL_PUBLIC = "asymmetric_master_secret_public";
-
+
public static MasterSecret changeMasterSecretPassphrase(Context context, String originalPassphrase, String newPassphrase) throws InvalidPassphraseException {
try {
MasterSecret masterSecret = getMasterSecret(context, originalPassphrase);
byte[] combinedSecrets = combineSecrets(masterSecret.getEncryptionKey().getEncoded(), masterSecret.getMacKey().getEncoded());
encryptWithPassphraseAndSave(context, combinedSecrets, newPassphrase);
-
+
return masterSecret;
} catch (GeneralSecurityException gse) {
throw new AssertionError(gse);
}
}
-
+
public static MasterSecret getMasterSecret(Context context, String passphrase) throws InvalidPassphraseException {
try {
byte[] encryptedAndMacdMasterSecret = retrieve(context, "master_secret");
@@ -70,7 +70,7 @@ public class MasterSecretUtil {
byte[] combinedSecrets = decryptWithPassphrase(context, encryptedMasterSecret, passphrase);
byte[] encryptionSecret = getEncryptionSecret(combinedSecrets);
byte[] macSecret = getMacSecret(combinedSecrets);
-
+
return new MasterSecret(new SecretKeySpec(encryptionSecret, "AES"),
new SecretKeySpec(macSecret, "HmacSHA1"));
} catch (GeneralSecurityException e) {
@@ -80,18 +80,18 @@ public class MasterSecretUtil {
Log.w("keyutil", e);
return null; //XXX
}
- }
-
+ }
+
public static AsymmetricMasterSecret getAsymmetricMasterSecret(Context context, MasterSecret masterSecret) {
try {
PublicKey publicKey = new PublicKey(retrieve(context, ASYMMETRIC_LOCAL_PUBLIC));
ECPrivateKeyParameters privateKey = null;
-
+
if (masterSecret != null) {
MasterCipher masterCipher = new MasterCipher(masterSecret);
privateKey = masterCipher.decryptKey(retrieve(context, "asymmetric_master_secret_private"));
}
-
+
return new AsymmetricMasterSecret(publicKey, privateKey);
} catch (InvalidKeyException ike) {
throw new AssertionError(ike);
@@ -99,93 +99,98 @@ public class MasterSecretUtil {
throw new AssertionError(e);
}
}
-
+
public static AsymmetricMasterSecret generateAsymmetricMasterSecret(Context context, MasterSecret masterSecret) {
- MasterCipher masterCipher = new MasterCipher(masterSecret);
+ MasterCipher masterCipher = new MasterCipher(masterSecret);
AsymmetricCipherKeyPair ackp = KeyUtil.generateKeyPair();
KeyPair keyPair = new KeyPair(31337, ackp, masterSecret);
PublicKey publicKey = keyPair.getPublicKey();
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters)ackp.getPrivate();
-
+
save(context, ASYMMETRIC_LOCAL_PUBLIC, publicKey.serialize());
save(context, "asymmetric_master_secret_private", masterCipher.encryptKey(privateKey));
-
+
return new AsymmetricMasterSecret(publicKey, privateKey);
}
-
+
public static MasterSecret generateMasterSecret(Context context, String passphrase) {
try {
byte[] encryptionSecret = generateEncryptionSecret();
byte[] macSecret = generateMacSecret();
byte[] masterSecret = combineSecrets(encryptionSecret, macSecret);
-
+
encryptWithPassphraseAndSave(context, masterSecret, passphrase);
-
- return new MasterSecret(new SecretKeySpec(encryptionSecret, "AES"),
+
+ return new MasterSecret(new SecretKeySpec(encryptionSecret, "AES"),
new SecretKeySpec(macSecret, "HmacSHA1"));
} catch (GeneralSecurityException e) {
Log.w("keyutil", e);
return null;
}
}
-
+
public static boolean hasAsymmericMasterSecret(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCES_NAME, 0);
- return settings.contains(ASYMMETRIC_LOCAL_PUBLIC);
+ return settings.contains(ASYMMETRIC_LOCAL_PUBLIC);
}
-
+
+ public static boolean isPassphraseInitialized(Context context) {
+ SharedPreferences preferences = context.getSharedPreferences(PREFERENCES_NAME, 0);
+ return preferences.getBoolean("passphrase_initialized", false);
+ }
+
private static void encryptWithPassphraseAndSave(Context context, byte[] masterSecret, String passphrase) throws GeneralSecurityException {
byte[] encryptedMasterSecret = encryptWithPassphrase(context, masterSecret, passphrase);
byte[] encryptedAndMacdMasterSecret = macWithPassphrase(context, encryptedMasterSecret, passphrase);
-
+
save(context, "master_secret", encryptedAndMacdMasterSecret);
- save(context, "passphrase_initialized", true);
+ save(context, "passphrase_initialized", true);
}
-
+
private static byte[] getEncryptionSecret(byte[] combinedSecrets) {
byte[] encryptionSecret = new byte[16];
System.arraycopy(combinedSecrets, 0, encryptionSecret, 0, encryptionSecret.length);
return encryptionSecret;
}
-
+
private static byte[] getMacSecret(byte[] combinedSecrets) {
byte[] macSecret = new byte[20];
System.arraycopy(combinedSecrets, 16, macSecret, 0, macSecret.length);
return macSecret;
}
-
+
private static byte[] combineSecrets(byte[] encryptionSecret, byte[] macSecret) {
byte[] combinedSecret = new byte[encryptionSecret.length + macSecret.length];
System.arraycopy(encryptionSecret, 0, combinedSecret, 0, encryptionSecret.length);
System.arraycopy(macSecret, 0, combinedSecret, encryptionSecret.length, macSecret.length);
-
+
return combinedSecret;
}
-
+
private static void save(Context context, String key, byte[] value) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCES_NAME, 0);
Editor editor = settings.edit();
-
+
editor.putString(key, Base64.encodeBytes(value));
- editor.commit();
+ editor.commit();
}
-
+
private static void save(Context context, String key, boolean value) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCES_NAME, 0);
Editor editor = settings.edit();
-
+
editor.putBoolean(key, value);
- editor.commit();
+ editor.commit();
}
-
+
private static byte[] retrieve(Context context, String key) throws IOException {
SharedPreferences settings = context.getSharedPreferences(PREFERENCES_NAME, 0);
String encodedValue = settings.getString(key, "");
-
+
if (encodedValue == "") return null;
else return Base64.decode(encodedValue);
}
-
+
private static byte[] generateEncryptionSecret() {
try {
KeyGenerator generator = KeyGenerator.getInstance("AES");
@@ -196,9 +201,9 @@ public class MasterSecretUtil {
} catch (NoSuchAlgorithmException ex) {
Log.w("keyutil", ex);
return null;
- }
+ }
}
-
+
private static byte[] generateMacSecret() {
try {
KeyGenerator generator = KeyGenerator.getInstance("HmacSHA1");
@@ -208,42 +213,42 @@ public class MasterSecretUtil {
return null;
}
}
-
+
private static byte[] generateSalt() throws NoSuchAlgorithmException {
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
byte[] salt = new byte[8];
random.nextBytes(salt);
-
+
return salt;
}
-
+
private static SecretKey getKeyFromPassphrase(String passphrase, byte[] salt) throws GeneralSecurityException {
PBEKeySpec keyspec = new PBEKeySpec(passphrase.toCharArray(), salt, 100);
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBEWITHSHA1AND128BITAES-CBC-BC");
return skf.generateSecret(keyspec);
}
-
- private static Cipher getCipherFromPassphrase(String passphrase, byte[] salt, int opMode) throws GeneralSecurityException {
+
+ private static Cipher getCipherFromPassphrase(String passphrase, byte[] salt, int opMode) throws GeneralSecurityException {
SecretKey key = getKeyFromPassphrase(passphrase, salt);
Cipher cipher = Cipher.getInstance(key.getAlgorithm());
cipher.init(opMode, key, new PBEParameterSpec(salt, 100));
-
+
return cipher;
}
-
+
private static byte[] encryptWithPassphrase(Context context, byte[] data, String passphrase) throws NoSuchAlgorithmException, GeneralSecurityException {
- byte[] encryptionSalt = generateSalt();
+ byte[] encryptionSalt = generateSalt();
Cipher cipher = getCipherFromPassphrase(passphrase, encryptionSalt, Cipher.ENCRYPT_MODE);
byte[] cipherText = cipher.doFinal(data);
-
- save(context, "encryption_salt", encryptionSalt);
+
+ save(context, "encryption_salt", encryptionSalt);
return cipherText;
}
-
+
private static byte[] decryptWithPassphrase(Context context, byte[] data, String passphrase) throws GeneralSecurityException, IOException {
byte[] encryptionSalt = retrieve(context, "encryption_salt");
Cipher cipher = getCipherFromPassphrase(passphrase, encryptionSalt, Cipher.DECRYPT_MODE);
- return cipher.doFinal(data);
+ return cipher.doFinal(data);
}
private static Mac getMacForPassphrase(String passphrase, byte[] salt) throws GeneralSecurityException {
@@ -255,32 +260,32 @@ public class MasterSecretUtil {
return hmac;
}
-
+
private static byte[] verifyMac(Context context, byte[] encryptedAndMacdData, String passphrase) throws InvalidPassphraseException, GeneralSecurityException, IOException {
byte[] macSalt = retrieve(context, "mac_salt");
- Mac hmac = getMacForPassphrase(passphrase, macSalt);
+ Mac hmac = getMacForPassphrase(passphrase, macSalt);
byte[] encryptedData = new byte[encryptedAndMacdData.length - hmac.getMacLength()];
System.arraycopy(encryptedAndMacdData, 0, encryptedData, 0, encryptedData.length);
-
+
byte[] givenMac = new byte[hmac.getMacLength()];
System.arraycopy(encryptedAndMacdData, encryptedAndMacdData.length-hmac.getMacLength(), givenMac, 0, givenMac.length);
byte[] localMac = hmac.doFinal(encryptedData);
-
+
if (Arrays.equals(givenMac, localMac)) return encryptedData;
- else throw new InvalidPassphraseException("MAC Error");
+ else throw new InvalidPassphraseException("MAC Error");
}
-
+
private static byte[] macWithPassphrase(Context context, byte[] data, String passphrase) throws GeneralSecurityException {
- byte[] macSalt = generateSalt();
+ byte[] macSalt = generateSalt();
Mac hmac = getMacForPassphrase(passphrase, macSalt);
byte[] mac = hmac.doFinal(data);
byte[] result = new byte[data.length + mac.length];
-
+
System.arraycopy(data, 0, result, 0, data.length);
System.arraycopy(mac, 0, result, data.length, mac.length);
-
+
save(context, "mac_salt", macSalt);
return result;
}
diff --git a/src/org/thoughtcrime/securesms/database/SmsMigrator.java b/src/org/thoughtcrime/securesms/database/SmsMigrator.java
index 92152b9531..86209f17df 100644
--- a/src/org/thoughtcrime/securesms/database/SmsMigrator.java
+++ b/src/org/thoughtcrime/securesms/database/SmsMigrator.java
@@ -150,7 +150,7 @@ public class SmsMigrator {
private static void migrateConversation(Context context, MasterSecret masterSecret,
SmsMigrationProgressListener listener,
- int primaryProgress,
+ ProgressDescription progress,
long theirThreadId, long ourThreadId)
{
SmsDatabase ourSmsDatabase = DatabaseFactory.getSmsDatabase(context);
@@ -166,11 +166,7 @@ public class SmsMigrator {
getContentValuesForRow(context, masterSecret, cursor, ourThreadId, statement);
statement.execute();
- double position = cursor.getPosition();
- double count = cursor.getCount();
- double progress = position / count;
-
- listener.progressUpdate(primaryProgress, (int)(progress * 10000));
+ listener.progressUpdate(new ProgressDescription(progress, cursor.getCount(), cursor.getPosition()));
}
ourSmsDatabase.endTransaction(transaction);
@@ -192,31 +188,26 @@ public class SmsMigrator {
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
Cursor cursor = null;
- int primaryProgress = 0;
try {
Uri threadListUri = Uri.parse("content://mms-sms/conversations?simple=true");
cursor = context.getContentResolver().query(threadListUri, null, null, null, "date ASC");
while (cursor != null && cursor.moveToNext()) {
- long theirThreadId = cursor.getLong(cursor.getColumnIndexOrThrow("_id"));
- String theirRecipients = cursor.getString(cursor.getColumnIndexOrThrow("recipient_ids"));
- Recipients ourRecipients = getOurRecipients(context, theirRecipients);
+ long theirThreadId = cursor.getLong(cursor.getColumnIndexOrThrow("_id"));
+ String theirRecipients = cursor.getString(cursor.getColumnIndexOrThrow("recipient_ids"));
+ Recipients ourRecipients = getOurRecipients(context, theirRecipients);
+ ProgressDescription progress = new ProgressDescription(cursor.getCount(), cursor.getPosition(), 100, 0);
if (ourRecipients != null) {
long ourThreadId = threadDatabase.getThreadIdFor(ourRecipients);
migrateConversation(context, masterSecret,
- listener, primaryProgress,
+ listener, progress,
theirThreadId, ourThreadId);
}
- double position = cursor.getPosition() + 1;
- double count = cursor.getCount();
- double progress = position / count;
-
- primaryProgress = (int)(progress * 10000);
-
- listener.progressUpdate(primaryProgress, 0);
+ progress.incrementPrimaryComplete();
+ listener.progressUpdate(progress);
}
} finally {
if (cursor != null)
@@ -228,6 +219,34 @@ public class SmsMigrator {
}
public interface SmsMigrationProgressListener {
- public void progressUpdate(int primaryProgress, int secondaryProgress);
+ public void progressUpdate(ProgressDescription description);
}
+
+ public static class ProgressDescription {
+ public final int primaryTotal;
+ public int primaryComplete;
+ public final int secondaryTotal;
+ public final int secondaryComplete;
+
+ public ProgressDescription(int primaryTotal, int primaryComplete,
+ int secondaryTotal, int secondaryComplete)
+ {
+ this.primaryTotal = primaryTotal;
+ this.primaryComplete = primaryComplete;
+ this.secondaryTotal = secondaryTotal;
+ this.secondaryComplete = secondaryComplete;
+ }
+
+ public ProgressDescription(ProgressDescription that, int secondaryTotal, int secondaryComplete) {
+ this.primaryComplete = that.primaryComplete;
+ this.primaryTotal = that.primaryTotal;
+ this.secondaryComplete = secondaryComplete;
+ this.secondaryTotal = secondaryTotal;
+ }
+
+ public void incrementPrimaryComplete() {
+ primaryComplete += 1;
+ }
+ }
+
}
diff --git a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java
index 0a3594e94d..c6c123cf2a 100644
--- a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java
+++ b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java
@@ -37,8 +37,8 @@ import android.text.TextUtils;
import android.util.Log;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
-import org.thoughtcrime.securesms.ConversationListActivity;
import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.RoutingActivity;
import org.thoughtcrime.securesms.contacts.ContactPhotoFactory;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
@@ -79,7 +79,7 @@ public class MessageNotifier {
if (visibleThread == threadId) {
sendInThreadNotification(context);
} else {
- Intent intent = new Intent(context, ConversationListActivity.class);
+ Intent intent = new Intent(context, RoutingActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra("recipients", recipients);
intent.putExtra("thread_id", threadId);
@@ -187,7 +187,7 @@ public class MessageNotifier {
notificationState.getMessageCount()));
builder.setContentText(String.format(context.getString(R.string.MessageNotifier_most_recent_from_s),
notifications.get(0).getRecipientName()));
- builder.setContentIntent(PendingIntent.getActivity(context, 0, new Intent(context, ConversationListActivity.class), 0));
+ builder.setContentIntent(PendingIntent.getActivity(context, 0, new Intent(context, RoutingActivity.class), 0));
InboxStyle style = new InboxStyle();
diff --git a/src/org/thoughtcrime/securesms/notifications/NotificationItem.java b/src/org/thoughtcrime/securesms/notifications/NotificationItem.java
index 50a4ac9814..a37a7aad97 100644
--- a/src/org/thoughtcrime/securesms/notifications/NotificationItem.java
+++ b/src/org/thoughtcrime/securesms/notifications/NotificationItem.java
@@ -6,7 +6,7 @@ import android.content.Intent;
import android.net.Uri;
import android.text.SpannableStringBuilder;
-import org.thoughtcrime.securesms.ConversationListActivity;
+import org.thoughtcrime.securesms.RoutingActivity;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.Util;
@@ -62,7 +62,7 @@ public class NotificationItem {
}
public PendingIntent getPendingIntent(Context context) {
- Intent intent = new Intent(context, ConversationListActivity.class);
+ Intent intent = new Intent(context, RoutingActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
if (recipients.getPrimaryRecipient() != null) {
diff --git a/src/org/thoughtcrime/securesms/service/ApplicationMigrationService.java b/src/org/thoughtcrime/securesms/service/ApplicationMigrationService.java
index fde554dfb4..464ad2f208 100644
--- a/src/org/thoughtcrime/securesms/service/ApplicationMigrationService.java
+++ b/src/org/thoughtcrime/securesms/service/ApplicationMigrationService.java
@@ -4,41 +4,64 @@ import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.BitmapFactory;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
-import android.widget.RemoteViews;
+import android.support.v4.app.NotificationCompat;
+import android.util.Log;
-import org.thoughtcrime.securesms.ConversationListActivity;
import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.RoutingActivity;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.SmsMigrator;
+import org.thoughtcrime.securesms.database.SmsMigrator.ProgressDescription;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
public class ApplicationMigrationService extends Service
implements SmsMigrator.SmsMigrationProgressListener
{
- public static final int PROGRESS_UPDATE = 1;
- public static final int PROGRESS_COMPLETE = 2;
+ public static final String MIGRATE_DATABASE = "org.thoughtcrime.securesms.ApplicationMigration.MIGRATE_DATABSE";
+ public static final String COMPLETED_ACTION = "org.thoughtcrime.securesms.ApplicationMigrationService.COMPLETED";
+ private static final String PREFERENCES_NAME = "SecureSMS";
+ private static final String DATABASE_MIGRATED = "migrated";
- public static final String MIGRATE_DATABASE = "org.thoughtcrime.securesms.ApplicationMigration.MIGRATE_DATABSE";
+ private final BroadcastReceiver completedReceiver = new CompletedReceiver();
+ private final Binder binder = new ApplicationMigrationBinder();
+ private final Executor executor = Executors.newSingleThreadExecutor();
- private final Binder binder = new ApplicationMigrationBinder();
- private boolean isMigrating = false;
- private Handler handler = null;
- private Notification notification = null;
+ private Handler handler = null;
+ private NotificationCompat.Builder notification = null;
+ private ImportState state = new ImportState(ImportState.STATE_IDLE, null);
@Override
- public void onStart(Intent intent, int startId) {
- if (intent == null) return;
+ public void onCreate() {
+ registerCompletedReceiver();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (intent == null) return START_NOT_STICKY;
if (intent.getAction() != null && intent.getAction().equals(MIGRATE_DATABASE)) {
- handleDatabaseMigration((MasterSecret)intent.getParcelableExtra("master_secret"));
+ executor.execute(new ImportRunnable(intent));
}
+
+ return START_NOT_STICKY;
+ }
+
+ @Override
+ public void onDestroy() {
+ unregisterCompletedReceiver();
}
@Override
@@ -46,70 +69,106 @@ public class ApplicationMigrationService extends Service
return binder;
}
- private void handleDatabaseMigration(final MasterSecret masterSecret) {
- this.notification = initializeBackgroundNotification();
-
- final PowerManager power = (PowerManager)getSystemService(Context.POWER_SERVICE);
- final WakeLock wakeLock = power.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Migration");
-
- new Thread() {
- @Override
- public void run() {
- try {
- wakeLock.acquire();
-
- setMigrating(true);
- SmsMigrator.migrateDatabase(ApplicationMigrationService.this,
- masterSecret,
- ApplicationMigrationService.this);
- setMigrating(false);
-
- if (handler != null) {
- handler.obtainMessage(PROGRESS_COMPLETE).sendToTarget();
- }
-
- stopForeground(true);
- } finally {
- wakeLock.release();
- stopService(new Intent(ApplicationMigrationService.this,
- ApplicationMigrationService.class));
- }
- }
- }.start();
+ public void setImportStateHandler(Handler handler) {
+ this.handler = handler;
}
- private Notification initializeBackgroundNotification() {
- Intent intent = new Intent(this, ConversationListActivity.class);
- Notification notification = new Notification(R.drawable.icon,
- getString(R.string.ApplicationMigrationService_migrating),
- System.currentTimeMillis());
+ private void registerCompletedReceiver() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(COMPLETED_ACTION);
- notification.flags = notification.flags | Notification.FLAG_ONGOING_EVENT;
- notification.contentView = new RemoteViews(getApplicationContext().getPackageName(),
- R.layout.migration_notification_progress);
+ registerReceiver(completedReceiver, filter);
+ }
- notification.contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
- notification.contentView.setImageViewResource(R.id.status_icon, R.drawable.icon);
- notification.contentView.setTextViewText(R.id.status_text,
- getString(R.string.ApplicationMigrationService_migrating_system_text_messages));
- notification.contentView.setProgressBar(R.id.status_progress, 10000, 0, false);
+ private void unregisterCompletedReceiver() {
+ unregisterReceiver(completedReceiver);
+ }
+
+ private void notifyImportComplete() {
+ Intent intent = new Intent();
+ intent.setAction(COMPLETED_ACTION);
+
+ sendOrderedBroadcast(intent, null);
+ }
+
+ @Override
+ public void progressUpdate(ProgressDescription progress) {
+ setState(new ImportState(ImportState.STATE_MIGRATING_IN_PROGRESS, progress));
+ }
+
+ public ImportState getState() {
+ return state;
+ }
+
+ private void setState(ImportState state) {
+ this.state = state;
+
+ if (handler != null) {
+ handler.obtainMessage(state.state, state.progress).sendToTarget();
+ }
+
+ if (state.progress != null && state.progress.secondaryComplete == 0) {
+ updateBackgroundNotification(state.progress.primaryTotal, state.progress.primaryComplete);
+ }
+ }
+
+ private void updateBackgroundNotification(int total, int complete) {
+ notification.setProgress(total, complete, false);
+
+ ((NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE))
+ .notify(4242, notification.build());
+ }
+
+ private NotificationCompat.Builder initializeBackgroundNotification() {
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
+
+ builder.setSmallIcon(R.drawable.icon_notification);
+ builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon_notification));
+ builder.setContentTitle(getString(R.string.ApplicationMigrationService_importing_text_messages));
+ builder.setContentText(getString(R.string.ApplicationMigrationService_import_in_progress));
+ builder.setOngoing(true);
+ builder.setProgress(100, 0, false);
+ builder.setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, RoutingActivity.class), 0));
stopForeground(true);
- startForeground(4242, notification);
+ startForeground(4242, builder.build());
- return notification;
+ return builder;
}
- private synchronized void setMigrating(boolean isMigrating) {
- this.isMigrating = isMigrating;
- }
+ private class ImportRunnable implements Runnable {
+ private final MasterSecret masterSecret;
- public synchronized boolean isMigrating() {
- return isMigrating;
- }
+ public ImportRunnable(Intent intent) {
+ this.masterSecret = intent.getParcelableExtra("master_secret");
+ Log.w("ApplicationMigrationService", "Service got mastersecret: " + masterSecret);
+ }
- public void setHandler(Handler handler) {
- this.handler = handler;
+ @Override
+ public void run() {
+ notification = initializeBackgroundNotification();
+ PowerManager powerManager = (PowerManager)getSystemService(Context.POWER_SERVICE);
+ WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Migration");
+
+ try {
+ wakeLock.acquire();
+
+ setState(new ImportState(ImportState.STATE_MIGRATING_BEGIN, null));
+
+ SmsMigrator.migrateDatabase(ApplicationMigrationService.this,
+ masterSecret,
+ ApplicationMigrationService.this);
+
+ setState(new ImportState(ImportState.STATE_MIGRATING_COMPLETE, null));
+
+ setDatabaseImported(ApplicationMigrationService.this);
+ stopForeground(true);
+ notifyImportComplete();
+ stopSelf();
+ } finally {
+ wakeLock.release();
+ }
+ }
}
public class ApplicationMigrationBinder extends Binder {
@@ -118,20 +177,44 @@ public class ApplicationMigrationService extends Service
}
}
- @Override
- public void progressUpdate(int primaryProgress, int secondaryProgress) {
- if (handler != null) {
- handler.obtainMessage(PROGRESS_UPDATE, primaryProgress, secondaryProgress).sendToTarget();
- }
+ private class CompletedReceiver 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("Import Complete");
+ builder.setContentText("TextSecure system database import is complete.");
+ builder.setContentIntent(PendingIntent.getActivity(context, 0, new Intent(context, RoutingActivity.class), 0));
+ builder.setWhen(System.currentTimeMillis());
+ builder.setDefaults(Notification.DEFAULT_VIBRATE);
+ builder.setAutoCancel(true);
- if (notification != null && secondaryProgress == 0) {
- notification.contentView.setProgressBar(R.id.status_progress, 10000, primaryProgress, false);
-
- NotificationManager notificationManager =
- (NotificationManager)getApplicationContext()
- .getSystemService(Context.NOTIFICATION_SERVICE);
-
- notificationManager.notify(4242, notification);
+ Notification notification = builder.build();
+ ((NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE)).notify(31337, notification);
}
}
+
+ public static class ImportState {
+ public static final int STATE_IDLE = 0;
+ public static final int STATE_MIGRATING_BEGIN = 1;
+ public static final int STATE_MIGRATING_IN_PROGRESS = 2;
+ public static final int STATE_MIGRATING_COMPLETE = 3;
+
+ public int state;
+ public ProgressDescription progress;
+
+ public ImportState(int state, ProgressDescription progress) {
+ this.state = state;
+ this.progress = progress;
+ }
+ }
+
+ public static boolean isDatabaseImported(Context context) {
+ return context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
+ .getBoolean(DATABASE_MIGRATED, false);
+ }
+
+ public static void setDatabaseImported(Context context) {
+ context.getSharedPreferences(PREFERENCES_NAME, 0).edit().putBoolean(DATABASE_MIGRATED, true).commit();
+ }
}
diff --git a/src/org/thoughtcrime/securesms/service/KeyCachingService.java b/src/org/thoughtcrime/securesms/service/KeyCachingService.java
index 89790405a7..9edbd47633 100644
--- a/src/org/thoughtcrime/securesms/service/KeyCachingService.java
+++ b/src/org/thoughtcrime/securesms/service/KeyCachingService.java
@@ -32,8 +32,9 @@ import android.util.Log;
import android.widget.RemoteViews;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
-import org.thoughtcrime.securesms.ConversationListActivity;
import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.RoutingActivity;
+import org.thoughtcrime.securesms.crypto.DecryptingQueue;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
@@ -54,8 +55,6 @@ public class KeyCachingService extends Service {
public static final String CLEAR_KEY_ACTION = "org.thoughtcrime.securesms.service.action.CLEAR_KEY";
public static final String ACTIVITY_START_EVENT = "org.thoughtcrime.securesms.service.action.ACTIVITY_START_EVENT";
public static final String ACTIVITY_STOP_EVENT = "org.thoughtcrime.securesms.service.action.ACTIVITY_STOP_EVENT";
- public static final String PREFERENCES_NAME = "SecureSMS-Preferences";
-
private PendingIntent pending;
private int activitiesRunning = 0;
@@ -75,6 +74,7 @@ public class KeyCachingService extends Service {
foregroundService();
broadcastNewSecret();
startTimeoutIfAppropriate();
+ DecryptingQueue.schedulePendingDecrypts(this, masterSecret);
new Thread() {
@Override
@@ -178,7 +178,9 @@ public class KeyCachingService extends Service {
Notification notification = new Notification(R.drawable.icon_cached,
getString(R.string.KeyCachingService_textsecure_passphrase_cached),
System.currentTimeMillis());
- Intent intent = new Intent(this, ConversationListActivity.class);
+ Intent intent = new Intent(this, RoutingActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+
PendingIntent launchIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
notification.setLatestEventInfo(getApplicationContext(),
getString(R.string.KeyCachingService_passphrase_cached),