mirror of
				https://github.com/oxen-io/session-android.git
				synced 2025-11-03 16:26:26 +00:00 
			
		
		
		
	Do migration in backgrounded service.
This commit is contained in:
		@@ -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);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user