provide progress bar for import backup. fixes #3809

This commit is contained in:
Daniel Gultsch 2020-07-09 18:52:46 +02:00
parent 71a56002fe
commit 9ab0fbe48c

View file

@ -1,5 +1,6 @@
package eu.siacs.conversations.services; package eu.siacs.conversations.services;
import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
@ -10,9 +11,21 @@ import android.database.sqlite.SQLiteDatabase;
import android.net.Uri; import android.net.Uri;
import android.os.Binder; import android.os.Binder;
import android.os.IBinder; import android.os.IBinder;
import android.provider.OpenableColumns;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.util.Log; import android.util.Log;
import com.google.common.base.Charsets;
import com.google.common.io.CountingInputStream;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.io.CipherInputStream;
import org.bouncycastle.crypto.modes.AEADBlockCipher;
import org.bouncycastle.crypto.modes.GCMBlockCipher;
import org.bouncycastle.crypto.params.AEADParameters;
import org.bouncycastle.crypto.params.KeyParameter;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.File; import java.io.File;
@ -33,10 +46,6 @@ import java.util.zip.GZIPInputStream;
import java.util.zip.ZipException; import java.util.zip.ZipException;
import javax.crypto.BadPaddingException; import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
@ -44,14 +53,9 @@ import eu.siacs.conversations.persistance.DatabaseBackend;
import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.ui.ManageAccountActivity; import eu.siacs.conversations.ui.ManageAccountActivity;
import eu.siacs.conversations.utils.BackupFileHeader; import eu.siacs.conversations.utils.BackupFileHeader;
import eu.siacs.conversations.utils.Compatibility;
import eu.siacs.conversations.utils.SerialSingleThreadExecutor; import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import static eu.siacs.conversations.services.ExportBackupService.CIPHERMODE;
import static eu.siacs.conversations.services.ExportBackupService.KEYTYPE;
import static eu.siacs.conversations.services.ExportBackupService.PROVIDER;
public class ImportBackupService extends Service { public class ImportBackupService extends Service {
private static final int NOTIFICATION_ID = 21; private static final int NOTIFICATION_ID = 21;
@ -117,9 +121,9 @@ public class ImportBackupService extends Service {
return running.get(); return running.get();
} }
public void loadBackupFiles(OnBackupFilesLoaded onBackupFilesLoaded) { public void loadBackupFiles(final OnBackupFilesLoaded onBackupFilesLoaded) {
executor.execute(() -> { executor.execute(() -> {
List<Jid> accounts = mDatabaseBackend.getAccountJids(false); final List<Jid> accounts = mDatabaseBackend.getAccountJids(false);
final ArrayList<BackupFile> backupFiles = new ArrayList<>(); final ArrayList<BackupFile> backupFiles = new ArrayList<>();
final Set<String> apps = new HashSet<>(Arrays.asList("Conversations", "Quicksy", getString(R.string.app_name))); final Set<String> apps = new HashSet<>(Arrays.asList("Conversations", "Quicksy", getString(R.string.app_name)));
for (String app : apps) { for (String app : apps) {
@ -128,7 +132,12 @@ public class ImportBackupService extends Service {
Log.d(Config.LOGTAG, "directory not found: " + directory.getAbsolutePath()); Log.d(Config.LOGTAG, "directory not found: " + directory.getAbsolutePath());
continue; continue;
} }
for (File file : directory.listFiles()) { final File[] files = directory.listFiles();
if (files == null) {
onBackupFilesLoaded.onBackupFilesLoaded(backupFiles);
return;
}
for (final File file : files) {
if (file.isFile() && file.getName().endsWith(".ceb")) { if (file.isFile() && file.getName().endsWith(".ceb")) {
try { try {
final BackupFile backupFile = BackupFile.read(file); final BackupFile backupFile = BackupFile.read(file);
@ -149,24 +158,67 @@ public class ImportBackupService extends Service {
} }
private void startForegroundService() { private void startForegroundService() {
startForeground(NOTIFICATION_ID, createImportBackupNotification(1, 0));
}
private void updateImportBackupNotification(final long total, final long current) {
final int max;
final int progress;
if (total == 0) {
max = 1;
progress = 0;
} else {
max = 100;
progress = (int) (current * 100 / total);
}
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
try {
notificationManager.notify(NOTIFICATION_ID, createImportBackupNotification(max, progress));
} catch (final RuntimeException e) {
Log.d(Config.LOGTAG, "unable to make notification", e);
}
}
private Notification createImportBackupNotification(final int max, final int progress) {
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup"); NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup");
mBuilder.setContentTitle(getString(R.string.restoring_backup)) mBuilder.setContentTitle(getString(R.string.restoring_backup))
.setSmallIcon(R.drawable.ic_unarchive_white_24dp) .setSmallIcon(R.drawable.ic_unarchive_white_24dp)
.setProgress(1, 0, true); .setProgress(max, progress, max == 1 && progress == 0);
startForeground(NOTIFICATION_ID, mBuilder.build()); return mBuilder.build();
} }
private boolean importBackup(Uri uri, String password) { private boolean importBackup(final Uri uri, final String password) {
Log.d(Config.LOGTAG, "importing backup from " + uri); Log.d(Config.LOGTAG, "importing backup from " + uri);
try { try {
SQLiteDatabase db = mDatabaseBackend.getWritableDatabase(); final SQLiteDatabase db = mDatabaseBackend.getWritableDatabase();
final InputStream inputStream; final InputStream inputStream;
if ("file".equals(uri.getScheme())) { final String path = uri.getPath();
inputStream = new FileInputStream(new File(uri.getPath())); final long fileSize;
if ("file".equals(uri.getScheme()) && path != null) {
final File file = new File(path);
inputStream = new FileInputStream(file);
fileSize = file.length();
} else { } else {
final Cursor returnCursor = getContentResolver().query(uri, null, null, null, null);
if (returnCursor == null) {
fileSize = 0;
} else {
returnCursor.moveToFirst();
fileSize = returnCursor.getLong(returnCursor.getColumnIndex(OpenableColumns.SIZE));
returnCursor.close();
}
inputStream = getContentResolver().openInputStream(uri); inputStream = getContentResolver().openInputStream(uri);
} }
final DataInputStream dataInputStream = new DataInputStream(inputStream); if (inputStream == null) {
synchronized (mOnBackupProcessedListeners) {
for (final OnBackupProcessed l : mOnBackupProcessedListeners) {
l.onBackupRestoreFailed();
}
}
return false;
}
final CountingInputStream countingInputStream = new CountingInputStream(inputStream);
final DataInputStream dataInputStream = new DataInputStream(countingInputStream);
final BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream); final BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream);
Log.d(Config.LOGTAG, backupFileHeader.toString()); Log.d(Config.LOGTAG, backupFileHeader.toString());
@ -179,15 +231,14 @@ public class ImportBackupService extends Service {
return false; return false;
} }
final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); final byte[] key = ExportBackupService.getKey(password, backupFileHeader.getSalt());
byte[] key = ExportBackupService.getKey(password, backupFileHeader.getSalt());
SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
IvParameterSpec ivSpec = new IvParameterSpec(backupFileHeader.getIv());
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher);
GZIPInputStream gzipInputStream = new GZIPInputStream(cipherInputStream); AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
BufferedReader reader = new BufferedReader(new InputStreamReader(gzipInputStream, "UTF-8")); cipher.init(false, new AEADParameters(new KeyParameter(key), 128, backupFileHeader.getIv()));
final CipherInputStream cipherInputStream = new CipherInputStream(countingInputStream, cipher);
final GZIPInputStream gzipInputStream = new GZIPInputStream(cipherInputStream);
BufferedReader reader = new BufferedReader(new InputStreamReader(gzipInputStream, Charsets.UTF_8));
String line; String line;
StringBuilder multiLineQuery = null; StringBuilder multiLineQuery = null;
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
@ -198,10 +249,12 @@ public class ImportBackupService extends Service {
if (count % 2 == 1) { if (count % 2 == 1) {
db.execSQL(multiLineQuery.toString()); db.execSQL(multiLineQuery.toString());
multiLineQuery = null; multiLineQuery = null;
updateImportBackupNotification(fileSize, countingInputStream.getCount());
} }
} else { } else {
if (count % 2 == 0) { if (count % 2 == 0) {
db.execSQL(line); db.execSQL(line);
updateImportBackupNotification(fileSize, countingInputStream.getCount());
} else { } else {
multiLineQuery = new StringBuilder(line); multiLineQuery = new StringBuilder(line);
} }
@ -220,7 +273,7 @@ public class ImportBackupService extends Service {
} }
} }
return true; return true;
} catch (Exception e) { } catch (final Exception e) {
Throwable throwable = e.getCause(); Throwable throwable = e.getCause();
final boolean reasonWasCrypto = throwable instanceof BadPaddingException || e instanceof ZipException; final boolean reasonWasCrypto = throwable instanceof BadPaddingException || e instanceof ZipException;
synchronized (mOnBackupProcessedListeners) { synchronized (mOnBackupProcessedListeners) {