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

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