Do migration in backgrounded service.

This commit is contained in:
Moxie Marlinspike 2012-08-02 20:23:41 -07:00
parent 45ae16e684
commit cffedb09a1
7 changed files with 379 additions and 732 deletions

View File

@ -87,8 +87,9 @@
<activity android:name=".ReceiveKeyActivity" android:theme="@android:style/Theme.Dialog" android:label="Complete Key Exchange" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout"></activity>
<activity android:name=".VerifyImportedIdentityActivity" android:theme="@android:style/Theme.Dialog" android:label="Verify Imported Identity" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout"></activity>
<service android:enabled="true" android:name=".service.KeyCachingService"></service>
<service android:enabled="true" android:name=".service.SendReceiveService"></service>
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>
<service android:enabled="true" android:name=".service.KeyCachingService"/>
<service android:enabled="true" android:name=".service.SendReceiveService"/>
<!-- <receiver android:name=".service.BootListener" -->
<!-- android:enabled="true" -->

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="5dp">
<ImageView android:id="@+id/status_icon"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_alignParentLeft="true" />
<RelativeLayout android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_toRightOf="@id/status_icon">
<TextView android:id="@+id/status_text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true" />
<ProgressBar android:id="@+id/status_progress"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/status_text"
android:progressDrawable="@android:drawable/progress_horizontal"
android:indeterminate="false"
android:indeterminateOnly="false" />
</RelativeLayout>
</RelativeLayout>

View File

@ -2,22 +2,26 @@ 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.database.SmsMigrator;
import org.thoughtcrime.securesms.service.ApplicationMigrationService;
public class ApplicationMigrationManager extends Handler {
public class ApplicationMigrationManager extends Handler implements Runnable {
private ProgressDialog progressDialog;
private ApplicationMigrationListener listener;
private final Context context;
private final MasterSecret masterSecret;
private ApplicationMigrationListener listener;
public ApplicationMigrationManager(Context context,
MasterSecret masterSecret)
{
@ -29,30 +33,38 @@ public class ApplicationMigrationManager extends Handler implements Runnable {
this.listener = listener;
}
public void run() {
SmsMigrator.migrateDatabase(context, masterSecret, this);
private void displayMigrationProgress() {
progressDialog = new ProgressDialog(context);
progressDialog.setTitle("Migrating Database");
progressDialog.setMessage("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("Copy System Text Message Database?");
alertBuilder.setMessage("Current versions of TextSecure use an encrypted database that is " +
alertBuilder.setMessage("TextSecure uses an encrypted database that is " +
"separate from the default system database. Would you like to " +
"copy your existing text messages into TextSecure's encrypted " +
"database? Your default system database will be unaffected.");
alertBuilder.setCancelable(false);
alertBuilder.setPositiveButton("Copy", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
progressDialog = new ProgressDialog(context);
progressDialog.setTitle("Migrating Database");
progressDialog.setMessage("Migrating your SMS database...");
progressDialog.setMax(10000);
progressDialog.setCancelable(false);
progressDialog.setIndeterminate(false);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.show();
new Thread(ApplicationMigrationManager.this).start();
displayMigrationProgress();
Intent intent = new Intent(context, ApplicationMigrationService.class);
intent.setAction(ApplicationMigrationService.MIGRATE_DATABASE);
intent.putExtra("master_secret", masterSecret);
context.startService(intent);
}
});
@ -71,16 +83,20 @@ public class ApplicationMigrationManager extends Handler implements Runnable {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case SmsMigrator.PROGRESS_UPDATE:
progressDialog.incrementProgressBy(message.arg1);
progressDialog.setSecondaryProgress(0);
case ApplicationMigrationService.PROGRESS_UPDATE:
if (progressDialog != null) {
progressDialog.setProgress(message.arg1);
progressDialog.setSecondaryProgress(message.arg2);
}
break;
case SmsMigrator.SECONDARY_PROGRESS_UPDATE:
progressDialog.incrementSecondaryProgressBy(message.arg1);
break;
case SmsMigrator.COMPLETE:
progressDialog.dismiss();
listener.applicationMigrationComplete();
case ApplicationMigrationService.PROGRESS_COMPLETE:
if (progressDialog != null) {
progressDialog.dismiss();
}
if (listener != null) {
listener.applicationMigrationComplete();
}
break;
}
}
@ -88,4 +104,19 @@ public class ApplicationMigrationManager extends Handler implements Runnable {
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) {}
};
}

View File

@ -22,10 +22,12 @@ import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
import android.widget.Toast;
import com.actionbarsherlock.app.SherlockPreferenceActivity;
import com.actionbarsherlock.view.MenuItem;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.crypto.IdentityKey;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
@ -41,7 +43,7 @@ import org.thoughtcrime.securesms.util.MemoryCleaner;
*
*/
public class ApplicationPreferencesActivity extends PreferenceActivity {
public class ApplicationPreferencesActivity extends SherlockPreferenceActivity {
private static final int PICK_IDENTITY_CONTACT = 1;
private static final int IMPORT_IDENTITY_ID = 2;
@ -69,6 +71,9 @@ public class ApplicationPreferencesActivity extends PreferenceActivity {
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
this.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
addPreferencesFromResource(R.xml.preferences);
this.findPreference(IDENTITY_PREF).setOnPreferenceClickListener(new IdentityPreferenceClickListener());
@ -113,6 +118,15 @@ public class ApplicationPreferencesActivity extends PreferenceActivity {
super.onDestroy();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home: finish(); return true;
}
return false;
}
private void handleIdentitySelection(Intent data) {
Uri contactData = data.getData();
if (contactData != null)

View File

@ -42,6 +42,7 @@ public class ConversationListActivity extends SherlockFragmentActivity
private BroadcastReceiver killActivityReceiver;
private BroadcastReceiver newKeyReceiver;
private ApplicationMigrationManager migrationManager;
private boolean havePromptedForPassphrase = false;
@ -262,20 +263,22 @@ public class ConversationListActivity extends SherlockFragmentActivity
}
private void initializeDatabaseMigration() {
ApplicationMigrationManager migrationManager = new ApplicationMigrationManager(this,
masterSecret);
ApplicationMigrationManager.ApplicationMigrationListener listener =
new ApplicationMigrationManager.ApplicationMigrationListener() {
@Override
public void applicationMigrationComplete() {
if (masterSecret != null)
DecryptingQueue.schedulePendingDecrypts(ConversationListActivity.this,
masterSecret);
}
};
if (migrationManager == null) {
migrationManager = new ApplicationMigrationManager(this, masterSecret);
migrationManager.setMigrationListener(listener);
migrationManager.migrate();
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 initializeKeyCachingServiceRegistration() {
@ -382,607 +385,4 @@ public class ConversationListActivity extends SherlockFragmentActivity
}
}
// @Override
// public boolean onPrepareOptionsMenu(Menu menu) {
// super.onPrepareOptionsMenu(menu);
//
// menu.clear();
//
// if (!batchMode) prepareNormalMenu(menu);
// else prepareBatchModeMenu(menu);
//
// return true;
// }
//
// private void prepareNormalMenu(Menu menu) {
// menu.add(0, MENU_BATCH_MODE, Menu.NONE, "Batch Mode")
// .setIcon(android.R.drawable.ic_menu_share);
//
// if (masterSecret != null) {
// menu.add(0, MENU_SEND_KEY, Menu.NONE, "Secure Session")
// .setIcon(R.drawable.ic_lock_message_sms);
// } else {
// menu.add(0, MENU_PASSPHRASE_KEY, Menu.NONE, "Enter passphrase")
// .setIcon(R.drawable.ic_lock_message_sms);
// }
//
// menu.add(0, MENU_NEW_MESSAGE, Menu.NONE, "New Message")
// .setIcon(R.drawable.ic_menu_msg_compose_holo_dark)
// .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
//
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
// android.widget.SearchView searchView =
// new android.widget.SearchView(this.getApplicationContext());
//
// menu.add(0, MENU_SEARCH, Menu.NONE, "Search")
// .setActionView(searchView)
// .setIcon(R.drawable.ic_menu_search_holo_dark)
// .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM |
// MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
//
//// initializeSearch(searchView);
// } else {
// ImageView image = new ImageView(this);
// image.setImageResource(R.drawable.ic_menu_search_holo_dark);
// image.setVisibility(View.INVISIBLE);
// FrameLayout searchView = new FrameLayout(this);
// searchView.addView(image);
//
// menu.add(0, -1, Menu.NONE, "")
// .setActionView(searchView)
// .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
// }
//
// menu.add(0, MENU_PREFERENCES_KEY, Menu.NONE, "Settings")
// .setIcon(android.R.drawable.ic_menu_preferences);
//
// SubMenu importExportMenu = menu.addSubMenu("Import/Export")
// .setIcon(android.R.drawable.ic_menu_save);
// importExportMenu.add(0, MENU_EXPORT, Menu.NONE, "Export To SD Card")
// .setIcon(android.R.drawable.ic_menu_save);
// importExportMenu.add(0, MENU_IMPORT, Menu.NONE, "Import From SD Card")
// .setIcon(android.R.drawable.ic_menu_revert);
//
// if (masterSecret != null) {
// menu.add(0, MENU_CLEAR_PASSPHRASE, Menu.NONE, "Clear Passphrase")
// .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
// }
// }
//
// private void prepareBatchModeMenu(Menu menu) {
// menu.add(0, MENU_EXIT_BATCH, Menu.NONE, "Normal Mode")
// .setIcon(android.R.drawable.ic_menu_set_as);
//
// menu.add(0, MENU_DELETE_SELECTED_THREADS, Menu.NONE, "Delete Selected")
// .setIcon(R.drawable.ic_menu_trash_holo_dark)
// .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
//
// menu.add(0, MENU_SELECT_ALL_THREADS, Menu.NONE, "Select All")
// .setIcon(android.R.drawable.ic_menu_add);
//
// menu.add(0, MENU_CLEAR_SELECTION, Menu.NONE, "Unselect All")
// .setIcon(android.R.drawable.ic_menu_revert);
// }
// @Override
// public boolean onOptionsItemSelected(MenuItem item) {
// super.onOptionsItemSelected(item);
//
// switch (item.getItemId()) {
// case MENU_NEW_MESSAGE:
// createConversation(-1, null);
// return true;
// case MENU_SEND_KEY:
// Intent intent = new Intent(this, SendKeyActivity.class);
// intent.putExtra("master_secret", masterSecret);
//
// startActivity(intent);
// return true;
// case MENU_PASSPHRASE_KEY:
// promptForPassphrase();
// return true;
//// case MENU_BATCH_MODE:
//// initiateBatchMode();
//// return true;
// case MENU_PREFERENCES_KEY:
// Intent preferencesIntent = new Intent(this, ApplicationPreferencesActivity.class);
// preferencesIntent.putExtra("master_secret", masterSecret);
// startActivity(preferencesIntent);
// return true;
// case MENU_EXPORT:
// ExportHandler exportHandler = new ExportHandler();
// exportHandler.export();
// return true;
// case MENU_IMPORT:
// ExportHandler importHandler = new ExportHandler();
// importHandler.importFromSd();
// return true;
//// case MENU_DELETE_SELECTED_THREADS: deleteSelectedThreads(); return true;
//// case MENU_SELECT_ALL_THREADS: selectAllThreads(); return true;
//// case MENU_CLEAR_SELECTION: unselectAllThreads(); return true;
//// case MENU_EXIT_BATCH: stopBatchMode(); return true;
//// case MENU_CLEAR_PASSPHRASE:
//// Intent keyService = new Intent(this, KeyCachingService.class);
//// keyService.setAction(KeyCachingService.CLEAR_KEY_ACTION);
//// startService(keyService);
//// addConversationItems();
//// promptForPassphrase();
//// // finish();
//// return true;
// }
//
// return false;
// }
// public void eulaComplete() {
// clearNotifications();
// initializeReceivers();
// checkCachingService();
// }
// @Override
// public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
// if (((AdapterView.AdapterContextMenuInfo)menuInfo).position > 0) {
// Cursor cursor = ((CursorAdapter)this.getListAdapter()).getCursor();
// String recipientId = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.RECIPIENT_IDS));
// Recipients recipients = RecipientFactory.getRecipientsForIds(this, recipientId);
//
// menu.add(0, VIEW_THREAD_ID, Menu.NONE, "View thread");
//
// if (recipients.isSingleRecipient()) {
// if (recipients.getPrimaryRecipient().getName() != null) {
// menu.add(0, VIEW_CONTACT_ID, Menu.NONE, "View contact");
// } else {
// menu.add(0, ADD_CONTACT_ID, Menu.NONE, "Add to contacts");
// }
// }
//
// menu.add(0, DELETE_THREAD_ID, Menu.NONE, "Delete thread");
// }
// }
// @Override
// public boolean onContextItemSelected(android.view.MenuItem item) {
// Cursor cursor = ((CursorAdapter)this.getListAdapter()).getCursor();
// long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.ID));
// String recipientId = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.RECIPIENT_IDS));
// Recipients recipients = RecipientFactory.getRecipientsForIds(this, recipientId);
//
// switch(item.getItemId()) {
// case VIEW_THREAD_ID:
// createConversation(threadId, recipients);
// return true;
// case VIEW_CONTACT_ID:
// viewContact(recipients.getPrimaryRecipient());
// return true;
// case ADD_CONTACT_ID:
// addContact(recipients.getPrimaryRecipient());
// return true;
// case DELETE_THREAD_ID:
// deleteThread(threadId);
// return true;
// }
// return false;
// }
// private void initiateBatchMode() {
// this.batchMode = true;
// ((ConversationListAdapter)this.getListAdapter()).initializeBatchMode(batchMode);
// }
//
// private void stopBatchMode() {
// this.batchMode = false;
// ((ConversationListAdapter)this.getListAdapter()).initializeBatchMode(batchMode);
// }
//
// private void selectAllThreads() {
// ((ConversationListAdapter)this.getListAdapter()).selectAllThreads();
// }
//
// private void unselectAllThreads() {
// ((ConversationListAdapter)this.getListAdapter()).unselectAllThreads();
// }
// private void deleteSelectedThreads() {
// AlertDialog.Builder alert = new AlertDialog.Builder(this);
// alert.setIcon(android.R.drawable.ic_dialog_alert);
// alert.setTitle("Delete threads?");
// alert.setMessage("Are you sure you wish to delete ALL selected conversation threads?");
// alert.setCancelable(true);
// alert.setPositiveButton("Delete", new DialogInterface.OnClickListener() {
// public void onClick(DialogInterface dialog, int which) {
// Set<Long> selectedConversations = ((ConversationListAdapter)getListAdapter()).getBatchSelections();
//
// if (!selectedConversations.isEmpty())
// DatabaseFactory.getThreadDatabase(SecureSMS.this).deleteConversations(selectedConversations);
// }
// });
// alert.setNegativeButton("Cancel", null);
// alert.show();
// }
// private void registerForContactsUpdates() {
// Log.w("SecureSMS", "Registering for contacts update...");
// getContentResolver().registerContentObserver(ContactAccessor.getInstance().getContactsUri(), true, new ContactUpdateObserver());
// }
// private void addContact(Recipient recipient) {
// RecipientFactory.getRecipientProvider().addContact(this, recipient);
// }
//
// private void viewContact(Recipient recipient) {
// if (recipient.getPersonId() > 0) {
// RecipientFactory.getRecipientProvider().viewContact(this, recipient);
// }
// }
// private void initializeWithMasterSecret(MasterSecret masterSecret) {
// this.masterSecret = masterSecret;
//
// if (masterSecret != null) {
// if (!IdentityKeyUtil.hasIdentityKey(this)) initializeIdentityKeys();
// if (!MasterSecretUtil.hasAsymmericMasterSecret(this)) initializeAsymmetricMasterSecret();
// if (!isDatabaseMigrated()) initializeDatabaseMigration();
// else DecryptingQueue.schedulePendingDecrypts(this, masterSecret);
// }
//
// this.fragment.setMasterSecret(masterSecret);
// createConversationIfNecessary(this.getIntent());
// }
//
// private void initializeAsymmetricMasterSecret() {
// new Thread(new AsymmetricMasteSecretInitializer()).start();
// }
//
// private void initializeIdentityKeys() {
// new Thread(new IdentityKeyInitializer()).start();
// }
// private void initializeSearch(android.widget.SearchView searchView) {
// searchView.setOnQueryTextListener(new android.widget.SearchView.OnQueryTextListener() {
// @Override
// public boolean onQueryTextSubmit(String query) {
// Cursor cursor = null;
//
// if (query.length() > 0) {
// List<String> numbers = ContactAccessor.getInstance().getNumbersForThreadSearchFilter(query, getContentResolver());
// cursor = DatabaseFactory.getThreadDatabase(SecureSMS.this).getFilteredConversationList(numbers);
// } else {
// cursor = DatabaseFactory.getThreadDatabase(SecureSMS.this).getConversationList();
// }
//
// if (cursor != null)
// startManagingCursor(cursor);
//
// if (getListAdapter() != null)
// ((CursorAdapter)getListAdapter()).changeCursor(cursor);
//
// return true;
// }
//
// @Override
// public boolean onQueryTextChange(String newText) {
// return onQueryTextSubmit(newText);
// }
// });
// }
// private void initializeReceivers() {
// this.newKeyReceiver = new BroadcastReceiver() {
// @Override
// public void onReceive(Context context, Intent intent) {
// Log.w("ConversationListActivity", "Got a key broadcast...");
// initializeWithMasterSecret((MasterSecret)intent.getParcelableExtra("master_secret"));
// }
// };
//
// IntentFilter filter = new IntentFilter(KeyCachingService.NEW_KEY_EVENT);
// registerReceiver(newKeyReceiver, filter, KeyCachingService.KEY_PERMISSION, null);
// }
// @Override
// protected void onListItemClick(ListView l, View v, int position, long id) {
// if (v instanceof ConversationHeaderView) {
// ConversationHeaderView headerView = (ConversationHeaderView) v;
// createConversation(headerView.getThreadId(), headerView.getRecipients());
// }
// }
// private void createConversationIfNecessary(Intent intent) {
// long thread = intent.getLongExtra("thread_id", -1L);
// Recipients recipients = null;
//
// if (intent.getAction() != null && intent.getAction().equals("android.intent.action.SENDTO")) {
// try {
// recipients = RecipientFactory.getRecipientsFromString(this, intent.getData().getSchemeSpecificPart());
// thread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipients);
// } catch (RecipientFormattingException rfe) {
// recipients = null;
// }
// } else {
// recipients = intent.getParcelableExtra("recipients");
// }
//
// if (recipients != null) {
// createConversation(thread, recipients);
// intent.putExtra("thread_id", -1L);
// intent.putExtra("recipients", (Parcelable)null);
// intent.setAction(null);
// }
// }
// private void createConversation(long threadId, Recipients recipients) {
// if (this.masterSecret == null) {
// promptForPassphrase();
// return;
// }
//
// Intent intent = new Intent(this, ComposeMessageActivity.class);
// intent.putExtra("recipients", recipients);
// intent.putExtra("thread_id", threadId);
// intent.putExtra("master_secret", masterSecret);
// startActivity(intent);
// }
// private void addConversationItems() {
//// Cursor cursor = DatabaseFactory.getThreadDatabase(this).getConversationList();
//// startManagingCursor(cursor);
// this.getLoaderManager();
////
// if (masterSecret == null) setListAdapter(new ConversationListAdapter(this, null));
// else setListAdapter(new DecryptingConversationListAdapter(this, null, masterSecret));
// }
// private void deleteThread(long threadId) {
// AlertDialog.Builder builder = new AlertDialog.Builder(this);
// builder.setTitle("Delete Thread Confirmation");
// builder.setIcon(android.R.drawable.ic_dialog_alert);
// builder.setCancelable(true);
// builder.setMessage("Are you sure that you want to permanently delete this conversation?");
// builder.setPositiveButton(R.string.yes, new DeleteThreadListener(threadId));
// builder.setNegativeButton(R.string.no, null);
// builder.show();
// }
// private class DeleteThreadListener implements DialogInterface.OnClickListener {
// private final long threadId;
//
// public DeleteThreadListener(long threadId) {
// this.threadId = threadId;
// }
//
// public void onClick(DialogInterface dialog, int which) {
// if (threadId > 0) {
// DatabaseFactory.getThreadDatabase(ConversationListActivity.this).deleteConversation(threadId);
// }
// }
// };
// private class NewKeyReceiver extends BroadcastReceiver {
// @Override
// public void onReceive(Context context, Intent intent) {
// Log.w("securesms", "Got a broadcast...");
// initializeWithMasterSecret((MasterSecret)intent.getParcelableExtra("master_secret"));
// }
// }
// private class KillActivityReceiver extends BroadcastReceiver {
// @Override
// public void onReceive(Context arg0, Intent arg1) {
// finish();
// }
// }
// private class SettingsClickListener implements View.OnClickListener {
// public void onClick(View v) {
// startActivity(new Intent(SecureSMS.this, ApplicationPreferencesActivity.class));
// }
// }
// private class ExportHandler extends Handler implements Runnable {
// private static final int ERROR_NO_SD = 0;
// private static final int ERROR_IO = 1;
// private static final int COMPLETE = 2;
//
// private static final int TASK_EXPORT = 0;
// private static final int TASK_IMPORT = 1;
//
// private int task;
// private ProgressDialog progressDialog;
//
// public void run() {
// try {
// switch (task) {
// case TASK_EXPORT: ApplicationExporter.exportToSd(ConversationListActivity.this); break;
// case TASK_IMPORT: ApplicationExporter.importFromSd(ConversationListActivity.this); break;
// }
// } catch (NoExternalStorageException e) {
// Log.w("SecureSMS", e);
// this.obtainMessage(ERROR_NO_SD).sendToTarget();
// return;
// } catch (IOException e) {
// Log.w("SecureSMS", e);
// this.obtainMessage(ERROR_IO).sendToTarget();
// return;
// }
//
// this.obtainMessage(COMPLETE).sendToTarget();
// }
//
// private void continueExport() {
// task = TASK_EXPORT;
// progressDialog = new ProgressDialog(ConversationListActivity.this);
// progressDialog.setTitle("Exporting Database and Keys");
// progressDialog.setMessage("Exporting your SMS database, keys, and settings...");
// progressDialog.setCancelable(false);
// progressDialog.setIndeterminate(true);
// progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
// progressDialog.show();
// new Thread(this).start();
// }
//
// private void continueImport() {
// task = TASK_IMPORT;
// progressDialog = new ProgressDialog(ConversationListActivity.this);
// progressDialog.setTitle("Importing Database and Keys");
// progressDialog.setMessage("Importnig your SMS database, keys, and settings...");
// progressDialog.setCancelable(false);
// progressDialog.setIndeterminate(true);
// progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
// progressDialog.show();
//
// initializeWithMasterSecret(null);
// Intent clearKeyIntent = new Intent(KeyCachingService.CLEAR_KEY_ACTION, null, ConversationListActivity.this, KeyCachingService.class);
// startService(clearKeyIntent);
//
// DatabaseFactory.getInstance(ConversationListActivity.this).close();
// new Thread(this).start();
// }
//
// public void importFromSd() {
// AlertDialog.Builder alertBuilder = new AlertDialog.Builder(ConversationListActivity.this);
// alertBuilder.setTitle("Import Database and Settings?");
// alertBuilder.setMessage("Import TextSecure database, keys, and settings from the SD Card?\n\nWARNING: This will clobber any existing messages, keys, and settings!");
// alertBuilder.setCancelable(false);
// alertBuilder.setPositiveButton("Import", new DialogInterface.OnClickListener() {
// public void onClick(DialogInterface dialog, int which) {
// continueImport();
// }
// });
// alertBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
// public void onClick(DialogInterface dialog, int which) {
// }
// });
// alertBuilder.create().show();
// }
//
// public void export() {
// AlertDialog.Builder alertBuilder = new AlertDialog.Builder(ConversationListActivity.this);
// alertBuilder.setTitle("Export Database?");
// alertBuilder.setMessage("Export TextSecure database, keys, and settings to the SD Card?");
// alertBuilder.setCancelable(false);
// alertBuilder.setPositiveButton("Export", new DialogInterface.OnClickListener() {
// public void onClick(DialogInterface dialog, int which) {
// continueExport();
// }
// });
// alertBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
// public void onClick(DialogInterface dialog, int which) {
// }
// });
// alertBuilder.create().show();
// }
//
// @Override
// public void handleMessage(Message message) {
// switch (message.what) {
// case ERROR_NO_SD:
// Toast.makeText(ConversationListActivity.this, "No SD card found!", Toast.LENGTH_LONG).show();
// break;
// case ERROR_IO:
// Toast.makeText(ConversationListActivity.this, "Error exporting to SD!", Toast.LENGTH_LONG).show();
// break;
// case COMPLETE:
// switch (task) {
// case TASK_IMPORT:
// Toast.makeText(ConversationListActivity.this, "Import Successful!", Toast.LENGTH_LONG).show();
//// addConversationItems();
// promptForPassphrase();
// break;
// case TASK_EXPORT:
// Toast.makeText(ConversationListActivity.this, "Export Successful!", Toast.LENGTH_LONG).show();
// break;
// }
// break;
// }
//
// progressDialog.dismiss();
// }
// }
// private class ContactUpdateObserver extends ContentObserver {
// public ContactUpdateObserver() {
// super(null);
// }
//
// @Override
// public void onChange(boolean selfChange) {
// super.onChange(selfChange);
// Log.w("SesureSMS", "Got contact update, clearing cache...");
// RecipientFactory.clearCache();
// }
// }
// private class MigrationHandler extends Handler implements Runnable {
// private ProgressDialog progressDialog;
//
// public void run() {
// SmsMigrator.migrateDatabase(ConversationListActivity.this, masterSecret, this);
// }
//
// private void continueMigration() {
// progressDialog = new ProgressDialog(ConversationListActivity.this);
// progressDialog.setTitle("Migrating Database");
// progressDialog.setMessage("Migrating your SMS database...");
// progressDialog.setMax(10000);
// progressDialog.setCancelable(false);
// progressDialog.setIndeterminate(false);
// progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
// progressDialog.show();
// new Thread(this).start();
// }
//
// private void cancelMigration() {
// ConversationListActivity.this.getSharedPreferences("SecureSMS", MODE_PRIVATE).edit().putBoolean("migrated", true).commit();
// ConversationListActivity.this.migrateDatabaseComplete();
// }
//
// public void migrate() {
// AlertDialog.Builder alertBuilder = new AlertDialog.Builder(ConversationListActivity.this);
// alertBuilder.setTitle("Copy System Text Message Database?");
// alertBuilder.setMessage("Current versions of TextSecure use an encrypted database that is separate from the default system database. Would you like to copy your existing text messages into TextSecure's encrypted database? Your default system database will be unaffected.");
// alertBuilder.setCancelable(false);
// alertBuilder.setPositiveButton("Copy", new DialogInterface.OnClickListener() {
// public void onClick(DialogInterface dialog, int which) {
// continueMigration();
// }
// });
// alertBuilder.setNegativeButton("Don't copy", new DialogInterface.OnClickListener() {
// public void onClick(DialogInterface dialog, int which) {
// cancelMigration();
// }
// });
// alertBuilder.create().show();
// }
//
// @Override
// public void handleMessage(Message message) {
// switch (message.what) {
// case SmsMigrator.PROGRESS_UPDATE:
// progressDialog.incrementProgressBy(message.arg1);
// progressDialog.setSecondaryProgress(0);
// break;
// case SmsMigrator.SECONDARY_PROGRESS_UPDATE:
// progressDialog.incrementSecondaryProgressBy(message.arg1);
// break;
// case SmsMigrator.COMPLETE:
// progressDialog.dismiss();
// ConversationListActivity.this.migrateDatabaseComplete();
// break;
// }
// }
// }
//
}

View File

@ -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,13 +10,18 @@
* 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 <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.database;
import java.util.StringTokenizer;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
import android.net.Uri;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
@ -25,47 +30,51 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import java.util.StringTokenizer;
public class SmsMigrator {
public static final int PROGRESS_UPDATE = 1;
public static final int SECONDARY_PROGRESS_UPDATE = 2;
public static final int COMPLETE = 3;
private static void addEncryptedStringToStatement(Context context, SQLiteStatement statement, Cursor cursor, MasterSecret masterSecret, int index, String key) {
private static void addEncryptedStringToStatement(Context context, SQLiteStatement statement,
Cursor cursor, MasterSecret masterSecret,
int index, String key)
{
int columnIndex = cursor.getColumnIndexOrThrow(key);
if (cursor.isNull(columnIndex))
if (cursor.isNull(columnIndex)) {
statement.bindNull(index);
else
} else {
statement.bindString(index, encryptIfNecessary(context, masterSecret, cursor.getString(columnIndex)));
}
private static void addStringToStatement(SQLiteStatement statement, Cursor cursor, int index, String key) {
int columnIndex = cursor.getColumnIndexOrThrow(key);
if (cursor.isNull(columnIndex))
statement.bindNull(index);
else
statement.bindString(index, cursor.getString(columnIndex));
}
}
private static void addIntToStatement(SQLiteStatement statement, Cursor cursor, int index, String key) {
private static void addStringToStatement(SQLiteStatement statement, Cursor cursor,
int index, String key)
{
int columnIndex = cursor.getColumnIndexOrThrow(key);
if (cursor.isNull(columnIndex))
if (cursor.isNull(columnIndex)) {
statement.bindNull(index);
else
statement.bindLong(index, cursor.getLong(columnIndex));
} else {
statement.bindString(index, cursor.getString(columnIndex));
}
}
private static void getContentValuesForRow(Context context, MasterSecret masterSecret, Cursor cursor, long threadId, SQLiteStatement statement) {
private static void addIntToStatement(SQLiteStatement statement, Cursor cursor,
int index, String key)
{
int columnIndex = cursor.getColumnIndexOrThrow(key);
if (cursor.isNull(columnIndex)) {
statement.bindNull(index);
} else {
statement.bindLong(index, cursor.getLong(columnIndex));
}
}
private static void getContentValuesForRow(Context context, MasterSecret masterSecret,
Cursor cursor, long threadId,
SQLiteStatement statement)
{
addStringToStatement(statement, cursor, 1, SmsDatabase.ADDRESS);
addIntToStatement(statement, cursor, 2, SmsDatabase.PERSON);
addIntToStatement(statement, cursor, 3, SmsDatabase.DATE);
@ -77,43 +86,46 @@ public class SmsMigrator {
addStringToStatement(statement, cursor, 9, SmsDatabase.SUBJECT);
addEncryptedStringToStatement(context, statement, cursor, masterSecret, 10, SmsDatabase.BODY);
addStringToStatement(statement, cursor, 11, SmsDatabase.SERVICE_CENTER);
statement.bindLong(12, threadId);
}
private static String getTheirCanonicalAddress(Context context, String theirRecipientId) {
Uri uri = Uri.parse("content://mms-sms/canonical-address/" + theirRecipientId);
Uri uri = Uri.parse("content://mms-sms/canonical-address/" + theirRecipientId);
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri, null, null, null, null);
if (cursor != null && cursor.moveToFirst())
return cursor.getString(0);
else
return null;
if (cursor != null && cursor.moveToFirst()) {
return cursor.getString(0);
} else {
return null;
}
} finally {
if (cursor != null)
cursor.close();
cursor.close();
}
}
private static Recipients getOurRecipients(Context context, String theirRecipients) {
StringTokenizer tokenizer = new StringTokenizer(theirRecipients.trim(), " ");
StringBuilder sb = new StringBuilder();
while (tokenizer.hasMoreTokens()) {
String theirRecipientId = tokenizer.nextToken();
String address = getTheirCanonicalAddress(context, theirRecipientId);
if (address == null)
continue;
continue;
if (sb.length() != 0)
sb.append(',');
sb.append(',');
sb.append(address);
}
try {
if (sb.length() == 0) return null;
else return RecipientFactory.getRecipientsFromString(context, sb.toString());
@ -122,75 +134,99 @@ public class SmsMigrator {
return null;
}
}
private static String encryptIfNecessary(Context context, MasterSecret masterSecret, String body) {
private static String encryptIfNecessary(Context context,
MasterSecret masterSecret,
String body)
{
if (!body.startsWith(Prefix.SYMMETRIC_ENCRYPT) && !body.startsWith(Prefix.ASYMMETRIC_ENCRYPT)) {
MasterCipher masterCipher = new MasterCipher(masterSecret);
return Prefix.SYMMETRIC_ENCRYPT + masterCipher.encryptBody(body);
}
return body;
}
private static void migrateConversation(Context context, MasterSecret masterSecret, Handler handler, long theirThreadId, long ourThreadId) {
private static void migrateConversation(Context context, MasterSecret masterSecret,
SmsMigrationProgressListener listener,
int primaryProgress,
long theirThreadId, long ourThreadId)
{
SmsDatabase ourSmsDatabase = DatabaseFactory.getSmsDatabase(context);
Cursor cursor = null;
try {
Uri uri = Uri.parse("content://sms/conversations/" + theirThreadId);
cursor = context.getContentResolver().query(uri, null, null, null, null);
SQLiteDatabase transaction = ourSmsDatabase.beginTransaction();
SQLiteStatement statement = ourSmsDatabase.createInsertStatement(transaction);
while (cursor != null && cursor.moveToNext()) {
getContentValuesForRow(context, masterSecret, cursor, ourThreadId, statement);
statement.execute();
Message msg = handler.obtainMessage(SECONDARY_PROGRESS_UPDATE, 10000/cursor.getCount(), 0);
handler.sendMessage(msg);
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));
}
ourSmsDatabase.endTransaction(transaction);
DatabaseFactory.getThreadDatabase(context).update(ourThreadId);
DatabaseFactory.getThreadDatabase(context).notifyConversationListeners(ourThreadId);
} finally {
if (cursor != null)
cursor.close();
cursor.close();
}
}
public static void migrateDatabase(Context context, MasterSecret masterSecret, Handler handler) {
public static void migrateDatabase(Context context,
MasterSecret masterSecret,
SmsMigrationProgressListener listener)
{
if (context.getSharedPreferences("SecureSMS", Context.MODE_PRIVATE).getBoolean("migrated", false))
return;
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
Cursor cursor = null;
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);
if (ourRecipients != null) {
long ourThreadId = threadDatabase.getThreadIdFor(ourRecipients);
migrateConversation(context, masterSecret, handler, theirThreadId, ourThreadId);
}
Message msg = handler.obtainMessage(PROGRESS_UPDATE, 10000/cursor.getCount(), 0);
handler.sendMessage(msg);
long theirThreadId = cursor.getLong(cursor.getColumnIndexOrThrow("_id"));
String theirRecipients = cursor.getString(cursor.getColumnIndexOrThrow("recipient_ids"));
Recipients ourRecipients = getOurRecipients(context, theirRecipients);
if (ourRecipients != null) {
long ourThreadId = threadDatabase.getThreadIdFor(ourRecipients);
migrateConversation(context, masterSecret,
listener, primaryProgress,
theirThreadId, ourThreadId);
}
double position = cursor.getPosition() + 1;
double count = cursor.getCount();
double progress = position / count;
primaryProgress = (int)(progress * 10000);
listener.progressUpdate(primaryProgress, 0);
}
} finally {
if (cursor != null)
cursor.close();
cursor.close();
}
context.getSharedPreferences("SecureSMS", Context.MODE_PRIVATE).edit().putBoolean("migrated", true).commit();
handler.sendEmptyMessage(COMPLETE);
context.getSharedPreferences("SecureSMS", Context.MODE_PRIVATE).edit()
.putBoolean("migrated", true).commit();
}
public interface SmsMigrationProgressListener {
public void progressUpdate(int primaryProgress, int secondaryProgress);
}
}

View File

@ -0,0 +1,133 @@
package org.thoughtcrime.securesms.service;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
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 org.thoughtcrime.securesms.ConversationListActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.SmsMigrator;
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";
private final Binder binder = new ApplicationMigrationBinder();
private boolean isMigrating = false;
private Handler handler = null;
private Notification notification = null;
@Override
public void onStart(Intent intent, int startId) {
if (intent.getAction() != null && intent.getAction().equals(MIGRATE_DATABASE)) {
handleDatabaseMigration((MasterSecret)intent.getParcelableExtra("master_secret"));
}
}
@Override
public IBinder onBind(Intent intent) {
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();
}
private Notification initializeBackgroundNotification() {
Intent intent = new Intent(this, ConversationListActivity.class);
Notification notification = new Notification(R.drawable.icon, "Migrating",
System.currentTimeMillis());
notification.flags = notification.flags | Notification.FLAG_ONGOING_EVENT;
notification.contentView = new RemoteViews(getApplicationContext().getPackageName(),
R.layout.migration_notification_progress);
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, "Migrating System Text Messages");
notification.contentView.setProgressBar(R.id.status_progress, 10000, 0, false);
stopForeground(true);
startForeground(4242, notification);
return notification;
}
private synchronized void setMigrating(boolean isMigrating) {
this.isMigrating = isMigrating;
}
public synchronized boolean isMigrating() {
return isMigrating;
}
public void setHandler(Handler handler) {
this.handler = handler;
}
public class ApplicationMigrationBinder extends Binder {
public ApplicationMigrationService getService() {
return ApplicationMigrationService.this;
}
}
@Override
public void progressUpdate(int primaryProgress, int secondaryProgress) {
if (handler != null) {
handler.obtainMessage(PROGRESS_UPDATE, primaryProgress, secondaryProgress).sendToTarget();
}
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);
}
}
}