1) Refactor recipient class to support asynchronous loading operations. 2) Refactor recipient factory to simplify recipient access. 3) Consoliate everything into one recipient provider that is capable of doing async lookups and intelligent caching.
232 lines
8.6 KiB
Java
232 lines
8.6 KiB
Java
/**
|
|
* 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
|
|
* (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.
|
|
*
|
|
* 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 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;
|
|
import org.thoughtcrime.securesms.protocol.Prefix;
|
|
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
|
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
|
import org.thoughtcrime.securesms.recipients.Recipients;
|
|
|
|
import java.util.StringTokenizer;
|
|
|
|
public class SmsMigrator {
|
|
|
|
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)) {
|
|
statement.bindNull(index);
|
|
} 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)
|
|
{
|
|
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);
|
|
addIntToStatement(statement, cursor, 4, SmsDatabase.PROTOCOL);
|
|
addIntToStatement(statement, cursor, 5, SmsDatabase.READ);
|
|
addIntToStatement(statement, cursor, 6, SmsDatabase.STATUS);
|
|
addIntToStatement(statement, cursor, 7, SmsDatabase.TYPE);
|
|
addIntToStatement(statement, cursor, 8, SmsDatabase.REPLY_PATH_PRESENT);
|
|
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);
|
|
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;
|
|
}
|
|
|
|
} finally {
|
|
if (cursor != null)
|
|
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;
|
|
|
|
if (sb.length() != 0)
|
|
sb.append(',');
|
|
|
|
sb.append(address);
|
|
}
|
|
|
|
try {
|
|
if (sb.length() == 0) return null;
|
|
else return RecipientFactory.getRecipientsFromString(context, sb.toString(), true);
|
|
} catch (RecipientFormattingException rfe) {
|
|
Log.w("SmsMigrator", rfe);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
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,
|
|
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();
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
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;
|
|
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,
|
|
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();
|
|
}
|
|
|
|
context.getSharedPreferences("SecureSMS", Context.MODE_PRIVATE).edit()
|
|
.putBoolean("migrated", true).commit();
|
|
}
|
|
|
|
public interface SmsMigrationProgressListener {
|
|
public void progressUpdate(int primaryProgress, int secondaryProgress);
|
|
}
|
|
}
|