@@ -5,6 +5,7 @@ import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
+import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Binder;
import android.os.IBinder;
@@ -20,13 +21,13 @@ import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.zip.GZIPInputStream;
-import javax.crypto.AEADBadTagException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
@@ -41,6 +42,7 @@ import eu.siacs.conversations.ui.ManageAccountActivity;
import eu.siacs.conversations.utils.BackupFileHeader;
import eu.siacs.conversations.utils.Compatibility;
import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
+import rocks.xmpp.addr.Jid;
import static eu.siacs.conversations.services.ExportBackupService.CIPHERMODE;
import static eu.siacs.conversations.services.ExportBackupService.KEYTYPE;
@@ -49,13 +51,10 @@ import static eu.siacs.conversations.services.ExportBackupService.PROVIDER;
public class ImportBackupService extends Service {
private static final int NOTIFICATION_ID = 21;
-
+ private static AtomicBoolean running = new AtomicBoolean(false);
private final ImportBackupServiceBinder binder = new ImportBackupServiceBinder();
private final SerialSingleThreadExecutor executor = new SerialSingleThreadExecutor(getClass().getSimpleName());
-
private final Set<OnBackupProcessed> mOnBackupProcessedListeners = Collections.newSetFromMap(new WeakHashMap<>());
-
- private static AtomicBoolean running = new AtomicBoolean(false);
private DatabaseBackend mDatabaseBackend;
private NotificationManager notificationManager;
@@ -85,7 +84,6 @@ public class ImportBackupService extends Service {
if (password == null || file == null) {
return START_NOT_STICKY;
}
- Log.d(Config.LOGTAG, "on start command");
if (running.compareAndSet(false, true)) {
executor.execute(() -> {
startForegroundService();
@@ -106,7 +104,8 @@ public class ImportBackupService extends Service {
public void loadBackupFiles(OnBackupFilesLoaded onBackupFilesLoaded) {
executor.execute(() -> {
final ArrayList<BackupFile> backupFiles = new ArrayList<>();
- for (String app : Arrays.asList("Conversations", "Quicksy")) {
+ final Set<String> apps = new HashSet<>(Arrays.asList("Conversations", "Quicksy", getString(R.string.app_name)));
+ for (String app : apps) {
final File directory = new File(FileBackend.getBackupDirectory(app));
if (!directory.exists() || !directory.isDirectory()) {
Log.d(Config.LOGTAG, "directory not found: " + directory.getAbsolutePath());
@@ -154,9 +153,11 @@ public class ImportBackupService extends Service {
BufferedReader reader = new BufferedReader(new InputStreamReader(gzipInputStream, "UTF-8"));
String line;
StringBuilder multiLineQuery = null;
+ int error = 0;
while ((line = reader.readLine()) != null) {
int count = count(line, '\'');
if (multiLineQuery != null) {
+ multiLineQuery.append('\n');
multiLineQuery.append(line);
if (count % 2 == 1) {
db.execSQL(multiLineQuery.toString());
@@ -171,6 +172,12 @@ public class ImportBackupService extends Service {
}
}
Log.d(Config.LOGTAG, "done reading file");
+ final Jid jid = backupFileHeader.getJid();
+ Cursor countCursor = db.rawQuery("select count(messages.uuid) from messages join conversations on conversations.uuid=messages.conversationUuid join accounts on conversations.accountUuid=accounts.uuid where accounts.username=? and accounts.server=?", new String[]{jid.getEscapedLocal(), jid.getDomain()});
+ countCursor.moveToFirst();
+ int count = countCursor.getInt(0);
+ Log.d(Config.LOGTAG, "restored " + count + " messages");
+ countCursor.close();
stopBackgroundService();
synchronized (mOnBackupProcessedListeners) {
for (OnBackupProcessed l : mOnBackupProcessedListeners) {
@@ -207,7 +214,7 @@ public class ImportBackupService extends Service {
.setAutoCancel(true)
.setContentIntent(PendingIntent.getActivity(this, 145, new Intent(this, ManageAccountActivity.class), PendingIntent.FLAG_UPDATE_CURRENT))
.setSmallIcon(R.drawable.ic_unarchive_white_24dp);
- notificationManager.notify(NOTIFICATION_ID,mBuilder.build());
+ notificationManager.notify(NOTIFICATION_ID, mBuilder.build());
}
private void stopBackgroundService() {
@@ -232,6 +239,18 @@ public class ImportBackupService extends Service {
return this.binder;
}
+ public interface OnBackupFilesLoaded {
+ void onBackupFilesLoaded(List<BackupFile> files);
+ }
+
+ public interface OnBackupProcessed {
+ void onBackupRestored();
+
+ void onBackupDecryptionFailed();
+
+ void onBackupRestoreFailed();
+ }
+
public static class BackupFile {
private final File file;
private final BackupFileHeader header;
@@ -263,14 +282,4 @@ public class ImportBackupService extends Service {
return ImportBackupService.this;
}
}
-
- public interface OnBackupFilesLoaded {
- void onBackupFilesLoaded(List<BackupFile> files);
- }
-
- public interface OnBackupProcessed {
- void onBackupRestored();
- void onBackupDecryptionFailed();
- void onBackupRestoreFailed();
- }
}
@@ -49,33 +49,14 @@ public class ExportBackupService extends Service {
public static final String PROVIDER = "BC";
private static final int NOTIFICATION_ID = 19;
+ private static final int PAGE_SIZE = 20;
private static AtomicBoolean running = new AtomicBoolean(false);
private DatabaseBackend mDatabaseBackend;
private List<Account> mAccounts;
private NotificationManager notificationManager;
- @Override
- public void onCreate() {
- mDatabaseBackend = DatabaseBackend.getInstance(getBaseContext());
- mAccounts = mDatabaseBackend.getAccounts();
- notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- if (running.compareAndSet(false, true)) {
- new Thread(() -> {
- export();
- stopForeground(true);
- running.set(false);
- stopSelf();
- }).start();
- }
- return START_NOT_STICKY;
- }
-
private static void accountExport(SQLiteDatabase db, String uuid, PrintWriter writer) {
- StringBuilder builder = new StringBuilder();
+ final StringBuilder builder = new StringBuilder();
final Cursor accountCursor = db.query(Account.TABLENAME, null, Account.UUID + "=?", new String[]{uuid}, null, null, null);
while (accountCursor != null && accountCursor.moveToNext()) {
builder.append("INSERT INTO ").append(Account.TABLENAME).append("(");
@@ -95,10 +76,8 @@ public class ExportBackupService extends Service {
builder.append("NULL");
} else if (value.matches("\\d+")) {
int intValue = Integer.parseInt(value);
- Log.d(Config.LOGTAG,"reading int value. "+intValue);
if (Account.OPTIONS.equals(accountCursor.getColumnName(i))) {
intValue |= 1 << Account.OPTION_DISABLED;
- Log.d(Config.LOGTAG,"modified int value "+intValue);
}
builder.append(intValue);
} else {
@@ -109,13 +88,103 @@ public class ExportBackupService extends Service {
builder.append(';');
builder.append('\n');
}
- Log.d(Config.LOGTAG,builder.toString());
if (accountCursor != null) {
accountCursor.close();
}
writer.append(builder.toString());
}
+ private static void simpleExport(SQLiteDatabase db, String table, String column, String uuid, PrintWriter writer) {
+ final Cursor cursor = db.query(table, null, column + "=?", new String[]{uuid}, null, null, null);
+ while (cursor != null && cursor.moveToNext()) {
+ writer.write(cursorToString(table, cursor, PAGE_SIZE));
+ }
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ public static byte[] getKey(String password, byte[] salt) {
+ try {
+ SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
+ return factory.generateSecret(new PBEKeySpec(password.toCharArray(), salt, 1024, 128)).getEncoded();
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ private static String cursorToString(String tablename, Cursor cursor, int max) {
+ return cursorToString(tablename, cursor, max, false);
+ }
+
+ private static String cursorToString(String tablename, Cursor cursor, int max, boolean ignore) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("INSERT ");
+ if (ignore) {
+ builder.append("OR IGNORE ");
+ }
+ builder.append("INTO ").append(tablename).append("(");
+ for (int i = 0; i < cursor.getColumnCount(); ++i) {
+ if (i != 0) {
+ builder.append(',');
+ }
+ builder.append(cursor.getColumnName(i));
+ }
+ builder.append(") VALUES");
+ for (int i = 0; i < max; ++i) {
+ if (i != 0) {
+ builder.append(',');
+ }
+ appendValues(cursor, builder);
+ if (i < max - 1 && !cursor.moveToNext()) {
+ break;
+ }
+ }
+ builder.append(';');
+ builder.append('\n');
+ return builder.toString();
+ }
+
+ private static void appendValues(Cursor cursor, StringBuilder builder) {
+ builder.append("(");
+ for (int i = 0; i < cursor.getColumnCount(); ++i) {
+ if (i != 0) {
+ builder.append(',');
+ }
+ final String value = cursor.getString(i);
+ if (value == null) {
+ builder.append("NULL");
+ } else if (value.matches("\\d+")) {
+ builder.append(value);
+ } else {
+ DatabaseUtils.appendEscapedSQLString(builder, value);
+ }
+ }
+ builder.append(")");
+
+ }
+
+ @Override
+ public void onCreate() {
+ mDatabaseBackend = DatabaseBackend.getInstance(getBaseContext());
+ mAccounts = mDatabaseBackend.getAccounts();
+ notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (running.compareAndSet(false, true)) {
+ new Thread(() -> {
+ export();
+ stopForeground(true);
+ running.set(false);
+ stopSelf();
+ }).start();
+ return START_STICKY;
+ }
+ return START_NOT_STICKY;
+ }
+
private void messageExport(SQLiteDatabase db, String uuid, PrintWriter writer, Progress progress) {
Cursor cursor = db.rawQuery("select messages.* from messages join conversations on conversations.uuid=messages.conversationUuid where conversations.accountUuid=?", new String[]{uuid});
int size = cursor != null ? cursor.getCount() : 0;
@@ -123,17 +192,16 @@ public class ExportBackupService extends Service {
int i = 0;
int p = 0;
while (cursor != null && cursor.moveToNext()) {
- writer.write(cursorToString(Message.TABLENAME, cursor, 20));
- if (i + 20 > size) {
+ writer.write(cursorToString(Message.TABLENAME, cursor, PAGE_SIZE, false));
+ if (i + PAGE_SIZE > size) {
i = size;
} else {
- i += 20;
+ i += PAGE_SIZE;
}
final int percentage = i * 100 / size;
if (p < percentage) {
p = percentage;
- notificationManager.notify(NOTIFICATION_ID,progress.build(p));
- Log.d(Config.LOGTAG, "percentage=" + p);
+ notificationManager.notify(NOTIFICATION_ID, progress.build(p));
}
}
if (cursor != null) {
@@ -141,16 +209,6 @@ public class ExportBackupService extends Service {
}
}
- private static void simpleExport(SQLiteDatabase db, String table, String column, String uuid, PrintWriter writer) {
- final Cursor cursor = db.query(table, null, column + "=?", new String[]{uuid}, null, null, null);
- while (cursor != null && cursor.moveToNext()) {
- writer.write(cursorToString(table, cursor, 20));
- }
- if (cursor != null) {
- cursor.close();
- }
- }
-
private void export() {
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup");
mBuilder.setContentTitle(getString(R.string.notification_create_backup_title))
@@ -166,11 +224,11 @@ public class ExportBackupService extends Service {
final byte[] salt = new byte[16];
secureRandom.nextBytes(IV);
secureRandom.nextBytes(salt);
- final BackupFileHeader backupFileHeader = new BackupFileHeader(getString(R.string.app_name),account.getJid(),System.currentTimeMillis(),IV,salt);
+ final BackupFileHeader backupFileHeader = new BackupFileHeader(getString(R.string.app_name), account.getJid(), System.currentTimeMillis(), IV, salt);
final Progress progress = new Progress(mBuilder, max, count);
- final File file = new File(FileBackend.getBackupDirectory(this)+account.getJid().asBareJid().toEscapedString()+".ceb");
+ final File file = new File(FileBackend.getBackupDirectory(this) + account.getJid().asBareJid().toEscapedString() + ".ceb");
if (file.getParentFile().mkdirs()) {
- Log.d(Config.LOGTAG,"created backup directory "+file.getParentFile().getAbsolutePath());
+ Log.d(Config.LOGTAG, "created backup directory " + file.getParentFile().getAbsolutePath());
}
final FileOutputStream fileOutputStream = new FileOutputStream(file);
final DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);
@@ -179,7 +237,7 @@ public class ExportBackupService extends Service {
final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER);
byte[] key = getKey(account.getPassword(), salt);
- Log.d(Config.LOGTAG,backupFileHeader.toString());
+ Log.d(Config.LOGTAG, backupFileHeader.toString());
SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
IvParameterSpec ivSpec = new IvParameterSpec(IV);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
@@ -192,8 +250,8 @@ public class ExportBackupService extends Service {
accountExport(db, uuid, writer);
simpleExport(db, Conversation.TABLENAME, Conversation.ACCOUNT, uuid, writer);
messageExport(db, uuid, writer, progress);
- for(String table : Arrays.asList(SQLiteAxolotlStore.PREKEY_TABLENAME, SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, SQLiteAxolotlStore.SESSION_TABLENAME, SQLiteAxolotlStore.IDENTITIES_TABLENAME)) {
- simpleExport(db, table, SQLiteAxolotlStore.ACCOUNT,uuid,writer);
+ for (String table : Arrays.asList(SQLiteAxolotlStore.PREKEY_TABLENAME, SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, SQLiteAxolotlStore.SESSION_TABLENAME, SQLiteAxolotlStore.IDENTITIES_TABLENAME)) {
+ simpleExport(db, table, SQLiteAxolotlStore.ACCOUNT, uuid, writer);
}
writer.flush();
writer.close();
@@ -205,58 +263,6 @@ public class ExportBackupService extends Service {
}
}
- public static byte[] getKey(String password, byte[] salt) {
- try {
- SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
- return factory.generateSecret(new PBEKeySpec(password.toCharArray(), salt, 1024, 128)).getEncoded();
- } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
- throw new AssertionError(e);
- }
- }
-
- private static String cursorToString(String tablename, Cursor cursor, int max) {
- StringBuilder builder = new StringBuilder();
- builder.append("INSERT INTO ").append(tablename).append("(");
- for (int i = 0; i < cursor.getColumnCount(); ++i) {
- if (i != 0) {
- builder.append(',');
- }
- builder.append(cursor.getColumnName(i));
- }
- builder.append(") VALUES");
- for (int i = 0; i < max; ++i) {
- if (i != 0) {
- builder.append(',');
- }
- appendValues(cursor, builder);
- if (!cursor.moveToNext()) {
- break;
- }
- }
- builder.append(';');
- builder.append('\n');
- return builder.toString();
- }
-
- private static void appendValues(Cursor cursor, StringBuilder builder) {
- builder.append("(");
- for (int i = 0; i < cursor.getColumnCount(); ++i) {
- if (i != 0) {
- builder.append(',');
- }
- final String value = cursor.getString(i);
- if (value == null) {
- builder.append("NULL");
- } else if (value.matches("\\d+")) {
- builder.append(value);
- } else {
- DatabaseUtils.appendEscapedSQLString(builder, value);
- }
- }
- builder.append(")");
-
- }
-
@Override
public IBinder onBind(Intent intent) {
return null;
@@ -274,7 +280,7 @@ public class ExportBackupService extends Service {
}
private Notification build(int percentage) {
- builder.setProgress(max * 100,count * 100 + percentage,false);
+ builder.setProgress(max * 100, count * 100 + percentage, false);
return builder.build();
}
}