From 9ab0fbe48c4bb4b602b4fbf35c109e509f99e381 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 9 Jul 2020 18:52:46 +0200 Subject: [PATCH] provide progress bar for import backup. fixes #3809 --- .../services/ImportBackupService.java | 109 +++++++++++++----- 1 file changed, 81 insertions(+), 28 deletions(-) diff --git a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java index 8596b1c3f..e50179c8d 100644 --- a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java +++ b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.services; +import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; @@ -10,9 +11,21 @@ import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.os.Binder; import android.os.IBinder; +import android.provider.OpenableColumns; import android.support.v4.app.NotificationCompat; +import android.support.v4.app.NotificationManagerCompat; 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.DataInputStream; import java.io.File; @@ -33,10 +46,6 @@ import java.util.zip.GZIPInputStream; import java.util.zip.ZipException; 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.R; @@ -44,14 +53,9 @@ import eu.siacs.conversations.persistance.DatabaseBackend; import eu.siacs.conversations.persistance.FileBackend; 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 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 { private static final int NOTIFICATION_ID = 21; @@ -117,9 +121,9 @@ public class ImportBackupService extends Service { return running.get(); } - public void loadBackupFiles(OnBackupFilesLoaded onBackupFilesLoaded) { + public void loadBackupFiles(final OnBackupFilesLoaded onBackupFilesLoaded) { executor.execute(() -> { - List accounts = mDatabaseBackend.getAccountJids(false); + final List accounts = mDatabaseBackend.getAccountJids(false); final ArrayList backupFiles = new ArrayList<>(); final Set apps = new HashSet<>(Arrays.asList("Conversations", "Quicksy", getString(R.string.app_name))); for (String app : apps) { @@ -128,7 +132,12 @@ public class ImportBackupService extends Service { Log.d(Config.LOGTAG, "directory not found: " + directory.getAbsolutePath()); 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")) { try { final BackupFile backupFile = BackupFile.read(file); @@ -149,24 +158,67 @@ public class ImportBackupService extends Service { } 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"); mBuilder.setContentTitle(getString(R.string.restoring_backup)) .setSmallIcon(R.drawable.ic_unarchive_white_24dp) - .setProgress(1, 0, true); - startForeground(NOTIFICATION_ID, mBuilder.build()); + .setProgress(max, progress, max == 1 && progress == 0); + 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); try { - SQLiteDatabase db = mDatabaseBackend.getWritableDatabase(); + final SQLiteDatabase db = mDatabaseBackend.getWritableDatabase(); final InputStream inputStream; - if ("file".equals(uri.getScheme())) { - inputStream = new FileInputStream(new File(uri.getPath())); + final String path = 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 { + 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); } - 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); Log.d(Config.LOGTAG, backupFileHeader.toString()); @@ -179,15 +231,14 @@ public class ImportBackupService extends Service { return false; } - final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); - 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); + final byte[] key = ExportBackupService.getKey(password, backupFileHeader.getSalt()); - GZIPInputStream gzipInputStream = new GZIPInputStream(cipherInputStream); - BufferedReader reader = new BufferedReader(new InputStreamReader(gzipInputStream, "UTF-8")); + AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); + 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; StringBuilder multiLineQuery = null; while ((line = reader.readLine()) != null) { @@ -198,10 +249,12 @@ public class ImportBackupService extends Service { if (count % 2 == 1) { db.execSQL(multiLineQuery.toString()); multiLineQuery = null; + updateImportBackupNotification(fileSize, countingInputStream.getCount()); } } else { if (count % 2 == 0) { db.execSQL(line); + updateImportBackupNotification(fileSize, countingInputStream.getCount()); } else { multiLineQuery = new StringBuilder(line); } @@ -220,7 +273,7 @@ public class ImportBackupService extends Service { } } return true; - } catch (Exception e) { + } catch (final Exception e) { Throwable throwable = e.getCause(); final boolean reasonWasCrypto = throwable instanceof BadPaddingException || e instanceof ZipException; synchronized (mOnBackupProcessedListeners) {