session-android/src/org/thoughtcrime/securesms/database/SmsMigrator.java

261 lines
9.8 KiB
Java
Raw Normal View History

2012-08-03 03:23:41 +00:00
/**
2011-12-20 18:20:44 +00:00
* Copyright (C) 2011 Whisper Systems
2012-08-03 03:23:41 +00:00
*
2011-12-20 18:20:44 +00:00
* 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
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* 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.
2012-08-03 03:23:41 +00:00
*
2011-12-20 18:20:44 +00:00
* 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;
2012-08-03 03:23:41 +00:00
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;
2011-12-20 18:20:44 +00:00
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
2012-08-03 03:23:41 +00:00
import java.util.StringTokenizer;
2011-12-20 18:20:44 +00:00
public class SmsMigrator {
2012-08-03 03:23:41 +00:00
private static void addEncryptedStringToStatement(Context context, SQLiteStatement statement,
Cursor cursor, MasterSecret masterSecret,
int index, String key)
{
2011-12-20 18:20:44 +00:00
int columnIndex = cursor.getColumnIndexOrThrow(key);
2012-08-03 03:23:41 +00:00
if (cursor.isNull(columnIndex)) {
2011-12-20 18:20:44 +00:00
statement.bindNull(index);
2012-08-03 03:23:41 +00:00
} else {
statement.bindString(index, encrypt(masterSecret, cursor.getString(columnIndex)));
2012-08-03 03:23:41 +00:00
}
2011-12-20 18:20:44 +00:00
}
2012-08-03 03:23:41 +00:00
private static void addStringToStatement(SQLiteStatement statement, Cursor cursor,
int index, String key)
{
2011-12-20 18:20:44 +00:00
int columnIndex = cursor.getColumnIndexOrThrow(key);
2012-08-03 03:23:41 +00:00
if (cursor.isNull(columnIndex)) {
2011-12-20 18:20:44 +00:00
statement.bindNull(index);
2012-08-03 03:23:41 +00:00
} else {
2011-12-20 18:20:44 +00:00
statement.bindString(index, cursor.getString(columnIndex));
2012-08-03 03:23:41 +00:00
}
2011-12-20 18:20:44 +00:00
}
2012-08-03 03:23:41 +00:00
private static void addIntToStatement(SQLiteStatement statement, Cursor cursor,
int index, String key)
{
2011-12-20 18:20:44 +00:00
int columnIndex = cursor.getColumnIndexOrThrow(key);
2012-08-03 03:23:41 +00:00
if (cursor.isNull(columnIndex)) {
2011-12-20 18:20:44 +00:00
statement.bindNull(index);
2012-08-03 03:23:41 +00:00
} else {
statement.bindLong(index, cursor.getLong(columnIndex));
}
2011-12-20 18:20:44 +00:00
}
2012-08-03 03:23:41 +00:00
private static void addTranslatedTypeToStatement(SQLiteStatement statement, Cursor cursor,
int index, String key)
{
int columnIndex = cursor.getColumnIndexOrThrow(key);
if (cursor.isNull(columnIndex)) {
statement.bindLong(index, SmsDatabase.Types.BASE_INBOX_TYPE | SmsDatabase.Types.ENCRYPTION_SYMMETRIC_BIT);
} else {
long theirType = cursor.getLong(columnIndex);
statement.bindLong(index, SmsDatabase.Types.translateFromSystemBaseType(theirType) | SmsDatabase.Types.ENCRYPTION_SYMMETRIC_BIT);
}
}
2012-08-03 03:23:41 +00:00
private static void getContentValuesForRow(Context context, MasterSecret masterSecret,
Cursor cursor, long threadId,
SQLiteStatement statement)
{
2011-12-20 18:20:44 +00:00
addStringToStatement(statement, cursor, 1, SmsDatabase.ADDRESS);
addIntToStatement(statement, cursor, 2, SmsDatabase.PERSON);
addIntToStatement(statement, cursor, 3, SmsDatabase.DATE_RECEIVED);
addIntToStatement(statement, cursor, 4, SmsDatabase.DATE_RECEIVED);
addIntToStatement(statement, cursor, 5, SmsDatabase.PROTOCOL);
addIntToStatement(statement, cursor, 6, SmsDatabase.READ);
addIntToStatement(statement, cursor, 7, SmsDatabase.STATUS);
addTranslatedTypeToStatement(statement, cursor, 8, SmsDatabase.TYPE);
addIntToStatement(statement, cursor, 9, SmsDatabase.REPLY_PATH_PRESENT);
addStringToStatement(statement, cursor, 10, SmsDatabase.SUBJECT);
addEncryptedStringToStatement(context, statement, cursor, masterSecret, 11, SmsDatabase.BODY);
addStringToStatement(statement, cursor, 12, SmsDatabase.SERVICE_CENTER);
2012-08-03 03:23:41 +00:00
statement.bindLong(13, threadId);
2011-12-20 18:20:44 +00:00
}
2012-08-03 03:23:41 +00:00
2011-12-20 18:20:44 +00:00
private static String getTheirCanonicalAddress(Context context, String theirRecipientId) {
2012-08-03 03:23:41 +00:00
Uri uri = Uri.parse("content://mms-sms/canonical-address/" + theirRecipientId);
2011-12-20 18:20:44 +00:00
Cursor cursor = null;
2012-08-03 03:23:41 +00:00
2011-12-20 18:20:44 +00:00
try {
cursor = context.getContentResolver().query(uri, null, null, null, null);
2012-08-03 03:23:41 +00:00
if (cursor != null && cursor.moveToFirst()) {
return cursor.getString(0);
} else {
return null;
}
} catch (IllegalStateException iae) {
Log.w("SmsMigrator", iae);
return null;
2011-12-20 18:20:44 +00:00
} finally {
if (cursor != null)
2012-08-03 03:23:41 +00:00
cursor.close();
2011-12-20 18:20:44 +00:00
}
}
2012-08-03 03:23:41 +00:00
2011-12-20 18:20:44 +00:00
private static Recipients getOurRecipients(Context context, String theirRecipients) {
StringTokenizer tokenizer = new StringTokenizer(theirRecipients.trim(), " ");
StringBuilder sb = new StringBuilder();
2012-08-03 03:23:41 +00:00
2011-12-20 18:20:44 +00:00
while (tokenizer.hasMoreTokens()) {
String theirRecipientId = tokenizer.nextToken();
String address = getTheirCanonicalAddress(context, theirRecipientId);
2012-08-03 03:23:41 +00:00
2011-12-20 18:20:44 +00:00
if (address == null)
2012-08-03 03:23:41 +00:00
continue;
2011-12-20 18:20:44 +00:00
if (sb.length() != 0)
2012-08-03 03:23:41 +00:00
sb.append(',');
2011-12-20 18:20:44 +00:00
sb.append(address);
}
2012-08-03 03:23:41 +00:00
2011-12-20 18:20:44 +00:00
try {
if (sb.length() == 0) return null;
else return RecipientFactory.getRecipientsFromString(context, sb.toString(), true);
2011-12-20 18:20:44 +00:00
} catch (RecipientFormattingException rfe) {
Log.w("SmsMigrator", rfe);
return null;
}
}
2012-08-03 03:23:41 +00:00
private static String encrypt(MasterSecret masterSecret, String body)
2012-08-03 03:23:41 +00:00
{
MasterCipher masterCipher = new MasterCipher(masterSecret);
return masterCipher.encryptBody(body);
2011-12-20 18:20:44 +00:00
}
2012-08-03 03:23:41 +00:00
private static void migrateConversation(Context context, MasterSecret masterSecret,
SmsMigrationProgressListener listener,
ProgressDescription progress,
2012-08-03 03:23:41 +00:00
long theirThreadId, long ourThreadId)
{
2011-12-20 18:20:44 +00:00
SmsDatabase ourSmsDatabase = DatabaseFactory.getSmsDatabase(context);
Cursor cursor = null;
2012-08-03 03:23:41 +00:00
2011-12-20 18:20:44 +00:00
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);
2012-08-03 03:23:41 +00:00
2011-12-20 18:20:44 +00:00
while (cursor != null && cursor.moveToNext()) {
2012-08-03 03:23:41 +00:00
getContentValuesForRow(context, masterSecret, cursor, ourThreadId, statement);
statement.execute();
listener.progressUpdate(new ProgressDescription(progress, cursor.getCount(), cursor.getPosition()));
2011-12-20 18:20:44 +00:00
}
2012-08-03 03:23:41 +00:00
2011-12-20 18:20:44 +00:00
ourSmsDatabase.endTransaction(transaction);
DatabaseFactory.getThreadDatabase(context).update(ourThreadId);
DatabaseFactory.getThreadDatabase(context).notifyConversationListeners(ourThreadId);
} finally {
if (cursor != null)
2012-08-03 03:23:41 +00:00
cursor.close();
2011-12-20 18:20:44 +00:00
}
}
2012-08-03 03:23:41 +00:00
public static void migrateDatabase(Context context,
MasterSecret masterSecret,
SmsMigrationProgressListener listener)
{
2011-12-20 18:20:44 +00:00
if (context.getSharedPreferences("SecureSMS", Context.MODE_PRIVATE).getBoolean("migrated", false))
return;
2012-08-03 03:23:41 +00:00
2011-12-20 18:20:44 +00:00
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
2012-08-03 03:23:41 +00:00
Cursor cursor = null;
2011-12-20 18:20:44 +00:00
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);
ProgressDescription progress = new ProgressDescription(cursor.getCount(), cursor.getPosition(), 100, 0);
2012-08-03 03:23:41 +00:00
if (ourRecipients != null) {
long ourThreadId = threadDatabase.getThreadIdFor(ourRecipients);
migrateConversation(context, masterSecret,
listener, progress,
2012-08-03 03:23:41 +00:00
theirThreadId, ourThreadId);
}
progress.incrementPrimaryComplete();
listener.progressUpdate(progress);
2011-12-20 18:20:44 +00:00
}
} finally {
if (cursor != null)
2012-08-03 03:23:41 +00:00
cursor.close();
2011-12-20 18:20:44 +00:00
}
2012-08-03 03:23:41 +00:00
context.getSharedPreferences("SecureSMS", Context.MODE_PRIVATE).edit()
.putBoolean("migrated", true).commit();
}
public interface SmsMigrationProgressListener {
public void progressUpdate(ProgressDescription description);
2011-12-20 18:20:44 +00:00
}
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;
}
}
2011-12-20 18:20:44 +00:00
}