add ability to cancel in-progress one-off backup
This commit is contained in:
parent
45b9c4dcc9
commit
5853f57f0a
|
@ -143,7 +143,11 @@
|
|||
</service>
|
||||
|
||||
<receiver
|
||||
android:name=".services.EventReceiver"
|
||||
android:name=".receiver.WorkManagerEventReceiver"
|
||||
android:exported="false" />
|
||||
|
||||
<receiver
|
||||
android:name=".receiver.SystemEventReceiver"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
|
@ -157,7 +161,7 @@
|
|||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".services.UnifiedPushDistributor"
|
||||
android:name=".receiver.UnifiedPushDistributor"
|
||||
android:enabled="false"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package eu.siacs.conversations.services;
|
||||
package eu.siacs.conversations.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
|
@ -10,9 +10,10 @@ import android.util.Log;
|
|||
import com.google.common.base.Strings;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.utils.Compatibility;
|
||||
|
||||
public class EventReceiver extends BroadcastReceiver {
|
||||
public class SystemEventReceiver extends BroadcastReceiver {
|
||||
|
||||
public static final String SETTING_ENABLED_ACCOUNTS = "enabled_accounts";
|
||||
public static final String EXTRA_NEEDS_FOREGROUND_SERVICE = "needs_foreground_service";
|
|
@ -1,4 +1,4 @@
|
|||
package eu.siacs.conversations.services;
|
||||
package eu.siacs.conversations.receiver;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
|
@ -25,6 +25,7 @@ import java.util.List;
|
|||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.persistance.UnifiedPushDatabase;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.utils.Compatibility;
|
||||
|
||||
public class UnifiedPushDistributor extends BroadcastReceiver {
|
|
@ -0,0 +1,32 @@
|
|||
package eu.siacs.conversations.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.work.WorkManager;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.ui.fragment.settings.BackupSettingsFragment;
|
||||
|
||||
public class WorkManagerEventReceiver extends BroadcastReceiver {
|
||||
|
||||
public static final String ACTION_STOP_BACKUP = "eu.siacs.conversations.receiver.STOP_BACKUP";
|
||||
|
||||
@Override
|
||||
public void onReceive(final Context context, final Intent intent) {
|
||||
final var action = Strings.nullToEmpty(intent == null ? null : intent.getAction());
|
||||
if (action.equals(ACTION_STOP_BACKUP)) {
|
||||
stopBackup(context);
|
||||
}
|
||||
}
|
||||
|
||||
private void stopBackup(final Context context) {
|
||||
Log.d(Config.LOGTAG, "trying to stop one-off backup worker");
|
||||
final var workManager = WorkManager.getInstance(context);
|
||||
workManager.cancelUniqueWork(BackupSettingsFragment.CREATE_ONE_OFF_BACKUP);
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ import java.util.List;
|
|||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.receiver.SystemEventReceiver;
|
||||
import eu.siacs.conversations.ui.ConversationsActivity;
|
||||
import eu.siacs.conversations.utils.Compatibility;
|
||||
|
||||
|
@ -44,7 +45,7 @@ public class ContactChooserTargetService extends ChooserTargetService implements
|
|||
@Override
|
||||
public List<ChooserTarget> onGetChooserTargets(
|
||||
final ComponentName targetActivityName, final IntentFilter matchedFilter) {
|
||||
if (!EventReceiver.hasEnabledAccounts(this)) {
|
||||
if (!SystemEventReceiver.hasEnabledAccounts(this)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
final Intent intent = new Intent(this, XmppConnectionService.class);
|
||||
|
|
|
@ -28,6 +28,7 @@ import eu.siacs.conversations.R;
|
|||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.parser.AbstractParser;
|
||||
import eu.siacs.conversations.persistance.UnifiedPushDatabase;
|
||||
import eu.siacs.conversations.receiver.UnifiedPushDistributor;
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
import eu.siacs.conversations.xml.Namespace;
|
||||
import eu.siacs.conversations.xmpp.Jid;
|
||||
|
|
|
@ -128,6 +128,7 @@ import eu.siacs.conversations.parser.PresenceParser;
|
|||
import eu.siacs.conversations.persistance.DatabaseBackend;
|
||||
import eu.siacs.conversations.persistance.FileBackend;
|
||||
import eu.siacs.conversations.persistance.UnifiedPushDatabase;
|
||||
import eu.siacs.conversations.receiver.SystemEventReceiver;
|
||||
import eu.siacs.conversations.ui.ChooseAccountForProfilePictureActivity;
|
||||
import eu.siacs.conversations.ui.ConversationsActivity;
|
||||
import eu.siacs.conversations.ui.RtpSessionActivity;
|
||||
|
@ -678,7 +679,7 @@ public class XmppConnectionService extends Service {
|
|||
@Override
|
||||
public int onStartCommand(final Intent intent, int flags, int startId) {
|
||||
final String action = Strings.nullToEmpty(intent == null ? null : intent.getAction());
|
||||
final boolean needsForegroundService = intent != null && intent.getBooleanExtra(EventReceiver.EXTRA_NEEDS_FOREGROUND_SERVICE, false);
|
||||
final boolean needsForegroundService = intent != null && intent.getBooleanExtra(SystemEventReceiver.EXTRA_NEEDS_FOREGROUND_SERVICE, false);
|
||||
if (needsForegroundService) {
|
||||
Log.d(Config.LOGTAG, "toggle forced foreground service after receiving event (action=" + action + ")");
|
||||
toggleForegroundService(true);
|
||||
|
@ -1286,7 +1287,7 @@ public class XmppConnectionService extends Service {
|
|||
this.accounts = databaseBackend.getAccounts();
|
||||
final SharedPreferences.Editor editor = getPreferences().edit();
|
||||
final boolean hasEnabledAccounts = hasEnabledAccounts();
|
||||
editor.putBoolean(EventReceiver.SETTING_ENABLED_ACCOUNTS, hasEnabledAccounts).apply();
|
||||
editor.putBoolean(SystemEventReceiver.SETTING_ENABLED_ACCOUNTS, hasEnabledAccounts).apply();
|
||||
editor.apply();
|
||||
toggleSetProfilePictureActivity(hasEnabledAccounts);
|
||||
reconfigurePushDistributor();
|
||||
|
@ -1582,7 +1583,7 @@ public class XmppConnectionService extends Service {
|
|||
return;
|
||||
}
|
||||
final long triggerAtMillis = SystemClock.elapsedRealtime() + (Config.POST_CONNECTIVITY_CHANGE_PING_INTERVAL * 1000);
|
||||
final Intent intent = new Intent(this, EventReceiver.class);
|
||||
final Intent intent = new Intent(this, SystemEventReceiver.class);
|
||||
intent.setAction(ACTION_POST_CONNECTIVITY_CHANGE);
|
||||
try {
|
||||
final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 1, intent, s()
|
||||
|
@ -1604,7 +1605,7 @@ public class XmppConnectionService extends Service {
|
|||
if (alarmManager == null) {
|
||||
return;
|
||||
}
|
||||
final Intent intent = new Intent(this, EventReceiver.class);
|
||||
final Intent intent = new Intent(this, SystemEventReceiver.class);
|
||||
intent.setAction(ACTION_PING);
|
||||
try {
|
||||
final PendingIntent pendingIntent =
|
||||
|
@ -1623,7 +1624,7 @@ public class XmppConnectionService extends Service {
|
|||
if (alarmManager == null) {
|
||||
return;
|
||||
}
|
||||
final Intent intent = new Intent(this, EventReceiver.class);
|
||||
final Intent intent = new Intent(this, SystemEventReceiver.class);
|
||||
intent.setAction(ACTION_IDLE_PING);
|
||||
try {
|
||||
final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, s()
|
||||
|
@ -2573,7 +2574,7 @@ public class XmppConnectionService extends Service {
|
|||
|
||||
private void syncEnabledAccountSetting() {
|
||||
final boolean hasEnabledAccounts = hasEnabledAccounts();
|
||||
getPreferences().edit().putBoolean(EventReceiver.SETTING_ENABLED_ACCOUNTS, hasEnabledAccounts).apply();
|
||||
getPreferences().edit().putBoolean(SystemEventReceiver.SETTING_ENABLED_ACCOUNTS, hasEnabledAccounts).apply();
|
||||
toggleSetProfilePictureActivity(hasEnabledAccounts);
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ import java.util.concurrent.TimeUnit;
|
|||
|
||||
public class BackupSettingsFragment extends XmppPreferenceFragment {
|
||||
|
||||
private static final String CREATE_ONE_OFF_BACKUP = "create_one_off_backup";
|
||||
public static final String CREATE_ONE_OFF_BACKUP = "create_one_off_backup";
|
||||
private static final String RECURRING_BACKUP = "recurring_backup";
|
||||
|
||||
private final ActivityResultLauncher<String> requestStorageForBackupLauncher =
|
||||
|
|
|
@ -13,7 +13,7 @@ import com.google.common.collect.ImmutableList;
|
|||
import com.google.common.collect.Lists;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.services.UnifiedPushDistributor;
|
||||
import eu.siacs.conversations.receiver.UnifiedPushDistributor;
|
||||
import eu.siacs.conversations.xmpp.Jid;
|
||||
|
||||
import java.net.URI;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package eu.siacs.conversations.utils;
|
||||
|
||||
import static eu.siacs.conversations.services.EventReceiver.EXTRA_NEEDS_FOREGROUND_SERVICE;
|
||||
import static eu.siacs.conversations.receiver.SystemEventReceiver.EXTRA_NEEDS_FOREGROUND_SERVICE;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ActivityOptions;
|
||||
|
|
|
@ -11,6 +11,7 @@ import android.content.pm.ServiceInfo;
|
|||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
@ -21,6 +22,7 @@ import androidx.work.WorkerParameters;
|
|||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
|
@ -31,6 +33,7 @@ import eu.siacs.conversations.entities.Conversation;
|
|||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.persistance.DatabaseBackend;
|
||||
import eu.siacs.conversations.persistance.FileBackend;
|
||||
import eu.siacs.conversations.receiver.WorkManagerEventReceiver;
|
||||
import eu.siacs.conversations.utils.BackupFileHeader;
|
||||
import eu.siacs.conversations.utils.Compatibility;
|
||||
|
||||
|
@ -65,7 +68,7 @@ import javax.crypto.spec.SecretKeySpec;
|
|||
public class ExportBackupWorker extends Worker {
|
||||
|
||||
private static final SimpleDateFormat DATE_FORMAT =
|
||||
new SimpleDateFormat("yyyy-MM-dd", Locale.US);
|
||||
new SimpleDateFormat("yyyy-MM-dd-HH-mm", Locale.US);
|
||||
|
||||
public static final String KEYTYPE = "AES";
|
||||
public static final String CIPHERMODE = "AES/GCM/NoPadding";
|
||||
|
@ -76,6 +79,11 @@ public class ExportBackupWorker extends Worker {
|
|||
private static final int NOTIFICATION_ID = 19;
|
||||
private static final int BACKUP_CREATED_NOTIFICATION_ID = 23;
|
||||
|
||||
private static final int PENDING_INTENT_FLAGS =
|
||||
s()
|
||||
? PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
|
||||
: PendingIntent.FLAG_UPDATE_CURRENT;
|
||||
|
||||
private final boolean recurringBackup;
|
||||
|
||||
public ExportBackupWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
|
||||
|
@ -99,9 +107,12 @@ public class ExportBackupWorker extends Worker {
|
|||
| NoSuchProviderException e) {
|
||||
Log.d(Config.LOGTAG, "could not create backup", e);
|
||||
return Result.failure();
|
||||
} finally {
|
||||
getApplicationContext()
|
||||
.getSystemService(NotificationManager.class)
|
||||
.cancel(NOTIFICATION_ID);
|
||||
}
|
||||
Log.d(Config.LOGTAG, "done creating " + files.size() + " backup files");
|
||||
getApplicationContext().getSystemService(NotificationManager.class).cancel(NOTIFICATION_ID);
|
||||
if (files.isEmpty() || recurringBackup) {
|
||||
return Result.success();
|
||||
}
|
||||
|
@ -113,13 +124,7 @@ public class ExportBackupWorker extends Worker {
|
|||
@Override
|
||||
public ForegroundInfo getForegroundInfo() {
|
||||
Log.d(Config.LOGTAG, "getForegroundInfo()");
|
||||
final var context = getApplicationContext();
|
||||
final NotificationCompat.Builder notification =
|
||||
new NotificationCompat.Builder(context, "backup");
|
||||
notification
|
||||
.setContentTitle(context.getString(R.string.notification_create_backup_title))
|
||||
.setSmallIcon(R.drawable.ic_archive_24dp)
|
||||
.setProgress(1, 0, false);
|
||||
final NotificationCompat.Builder notification = getNotification();
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
|
||||
return new ForegroundInfo(
|
||||
NOTIFICATION_ID,
|
||||
|
@ -144,10 +149,13 @@ public class ExportBackupWorker extends Worker {
|
|||
|
||||
int count = 0;
|
||||
final int max = accounts.size();
|
||||
final SecureRandom secureRandom = new SecureRandom();
|
||||
final List<File> files = new ArrayList<>();
|
||||
final ImmutableList.Builder<File> files = new ImmutableList.Builder<>();
|
||||
Log.d(Config.LOGTAG, "starting backup for " + max + " accounts");
|
||||
for (final Account account : accounts) {
|
||||
if (isStopped()) {
|
||||
Log.d(Config.LOGTAG, "ExportBackupWorker has stopped. Returning what we have");
|
||||
return files.build();
|
||||
}
|
||||
final String password = account.getPassword();
|
||||
if (Strings.nullToEmpty(password).trim().isEmpty()) {
|
||||
Log.d(
|
||||
|
@ -155,84 +163,140 @@ public class ExportBackupWorker extends Worker {
|
|||
String.format(
|
||||
"skipping backup for %s because password is empty. unable to encrypt",
|
||||
account.getJid().asBareJid()));
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
Log.d(
|
||||
Config.LOGTAG,
|
||||
String.format(
|
||||
"exporting data for account %s (%s)",
|
||||
account.getJid().asBareJid(), account.getUuid()));
|
||||
final byte[] IV = new byte[12];
|
||||
final byte[] salt = new byte[16];
|
||||
secureRandom.nextBytes(IV);
|
||||
secureRandom.nextBytes(salt);
|
||||
final BackupFileHeader backupFileHeader =
|
||||
new BackupFileHeader(
|
||||
context.getString(R.string.app_name),
|
||||
account.getJid(),
|
||||
System.currentTimeMillis(),
|
||||
IV,
|
||||
salt);
|
||||
final NotificationCompat.Builder notification =
|
||||
new NotificationCompat.Builder(context, "backup");
|
||||
notification
|
||||
.setContentTitle(context.getString(R.string.notification_create_backup_title))
|
||||
.setSmallIcon(R.drawable.ic_archive_24dp)
|
||||
.setProgress(1, 0, false);
|
||||
final Progress progress = new Progress(notification, max, count);
|
||||
final String filename =
|
||||
String.format(
|
||||
"%s.%s.ceb",
|
||||
account.getJid().asBareJid().toEscapedString(),
|
||||
DATE_FORMAT.format(new Date()));
|
||||
final File file = new File(FileBackend.getBackupDirectory(context), filename);
|
||||
try {
|
||||
export(database, account, password, file, max, count);
|
||||
} catch (final WorkStoppedException e) {
|
||||
if (file.delete()) {
|
||||
Log.d(
|
||||
Config.LOGTAG,
|
||||
"deleted in progress backup file " + file.getAbsolutePath());
|
||||
}
|
||||
Log.d(Config.LOGTAG, "ExportBackupWorker has stopped. Returning what we have");
|
||||
return files.build();
|
||||
}
|
||||
files.add(file);
|
||||
final File directory = file.getParentFile();
|
||||
if (directory != null && directory.mkdirs()) {
|
||||
Log.d(Config.LOGTAG, "created backup directory " + directory.getAbsolutePath());
|
||||
}
|
||||
final FileOutputStream fileOutputStream = new FileOutputStream(file);
|
||||
final DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);
|
||||
backupFileHeader.write(dataOutputStream);
|
||||
dataOutputStream.flush();
|
||||
|
||||
final Cipher cipher =
|
||||
Compatibility.twentyEight()
|
||||
? Cipher.getInstance(CIPHERMODE)
|
||||
: Cipher.getInstance(CIPHERMODE, PROVIDER);
|
||||
final byte[] key = getKey(password, salt);
|
||||
SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(IV);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
|
||||
CipherOutputStream cipherOutputStream =
|
||||
new CipherOutputStream(fileOutputStream, cipher);
|
||||
|
||||
final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(cipherOutputStream);
|
||||
final JsonWriter jsonWriter =
|
||||
new JsonWriter(
|
||||
new OutputStreamWriter(gzipOutputStream, StandardCharsets.UTF_8));
|
||||
jsonWriter.beginArray();
|
||||
final SQLiteDatabase db = database.getReadableDatabase();
|
||||
final String uuid = account.getUuid();
|
||||
accountExport(db, uuid, jsonWriter);
|
||||
simpleExport(db, Conversation.TABLENAME, Conversation.ACCOUNT, uuid, jsonWriter);
|
||||
messageExport(db, uuid, jsonWriter, progress);
|
||||
for (final String table :
|
||||
Arrays.asList(
|
||||
SQLiteAxolotlStore.PREKEY_TABLENAME,
|
||||
SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
|
||||
SQLiteAxolotlStore.SESSION_TABLENAME,
|
||||
SQLiteAxolotlStore.IDENTITIES_TABLENAME)) {
|
||||
simpleExport(db, table, SQLiteAxolotlStore.ACCOUNT, uuid, jsonWriter);
|
||||
}
|
||||
jsonWriter.endArray();
|
||||
jsonWriter.flush();
|
||||
jsonWriter.close();
|
||||
mediaScannerScanFile(file);
|
||||
Log.d(Config.LOGTAG, "written backup to " + file.getAbsoluteFile());
|
||||
count++;
|
||||
}
|
||||
return files;
|
||||
return files.build();
|
||||
}
|
||||
|
||||
private void export(
|
||||
final DatabaseBackend database,
|
||||
final Account account,
|
||||
final String password,
|
||||
final File file,
|
||||
final int max,
|
||||
final int count)
|
||||
throws IOException,
|
||||
InvalidKeySpecException,
|
||||
InvalidAlgorithmParameterException,
|
||||
InvalidKeyException,
|
||||
NoSuchPaddingException,
|
||||
NoSuchAlgorithmException,
|
||||
NoSuchProviderException,
|
||||
WorkStoppedException {
|
||||
final var context = getApplicationContext();
|
||||
final SecureRandom secureRandom = new SecureRandom();
|
||||
Log.d(
|
||||
Config.LOGTAG,
|
||||
String.format(
|
||||
"exporting data for account %s (%s)",
|
||||
account.getJid().asBareJid(), account.getUuid()));
|
||||
final byte[] IV = new byte[12];
|
||||
final byte[] salt = new byte[16];
|
||||
secureRandom.nextBytes(IV);
|
||||
secureRandom.nextBytes(salt);
|
||||
final BackupFileHeader backupFileHeader =
|
||||
new BackupFileHeader(
|
||||
context.getString(R.string.app_name),
|
||||
account.getJid(),
|
||||
System.currentTimeMillis(),
|
||||
IV,
|
||||
salt);
|
||||
final var notification = getNotification();
|
||||
if (!recurringBackup) {
|
||||
final var cancel = new Intent(context, WorkManagerEventReceiver.class);
|
||||
cancel.setAction(WorkManagerEventReceiver.ACTION_STOP_BACKUP);
|
||||
final var cancelPendingIntent =
|
||||
PendingIntent.getBroadcast(context, 197, cancel, PENDING_INTENT_FLAGS);
|
||||
notification.addAction(
|
||||
new NotificationCompat.Action.Builder(
|
||||
R.drawable.ic_cancel_24dp,
|
||||
context.getString(R.string.cancel),
|
||||
cancelPendingIntent)
|
||||
.build());
|
||||
}
|
||||
final Progress progress = new Progress(notification, max, count);
|
||||
final File directory = file.getParentFile();
|
||||
if (directory != null && directory.mkdirs()) {
|
||||
Log.d(Config.LOGTAG, "created backup directory " + directory.getAbsolutePath());
|
||||
}
|
||||
final FileOutputStream fileOutputStream = new FileOutputStream(file);
|
||||
final DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);
|
||||
backupFileHeader.write(dataOutputStream);
|
||||
dataOutputStream.flush();
|
||||
|
||||
final Cipher cipher =
|
||||
Compatibility.twentyEight()
|
||||
? Cipher.getInstance(CIPHERMODE)
|
||||
: Cipher.getInstance(CIPHERMODE, PROVIDER);
|
||||
final byte[] key = getKey(password, salt);
|
||||
SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(IV);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
|
||||
CipherOutputStream cipherOutputStream = new CipherOutputStream(fileOutputStream, cipher);
|
||||
|
||||
final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(cipherOutputStream);
|
||||
final JsonWriter jsonWriter =
|
||||
new JsonWriter(new OutputStreamWriter(gzipOutputStream, StandardCharsets.UTF_8));
|
||||
jsonWriter.beginArray();
|
||||
final SQLiteDatabase db = database.getReadableDatabase();
|
||||
final String uuid = account.getUuid();
|
||||
accountExport(db, uuid, jsonWriter);
|
||||
simpleExport(db, Conversation.TABLENAME, Conversation.ACCOUNT, uuid, jsonWriter);
|
||||
messageExport(db, uuid, jsonWriter, progress);
|
||||
for (final String table :
|
||||
Arrays.asList(
|
||||
SQLiteAxolotlStore.PREKEY_TABLENAME,
|
||||
SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
|
||||
SQLiteAxolotlStore.SESSION_TABLENAME,
|
||||
SQLiteAxolotlStore.IDENTITIES_TABLENAME)) {
|
||||
throwIfWorkStopped();
|
||||
simpleExport(db, table, SQLiteAxolotlStore.ACCOUNT, uuid, jsonWriter);
|
||||
}
|
||||
jsonWriter.endArray();
|
||||
jsonWriter.flush();
|
||||
jsonWriter.close();
|
||||
mediaScannerScanFile(file);
|
||||
Log.d(Config.LOGTAG, "written backup to " + file.getAbsoluteFile());
|
||||
}
|
||||
|
||||
private NotificationCompat.Builder getNotification() {
|
||||
final var context = getApplicationContext();
|
||||
final NotificationCompat.Builder notification =
|
||||
new NotificationCompat.Builder(context, "backup");
|
||||
notification
|
||||
.setContentTitle(context.getString(R.string.notification_create_backup_title))
|
||||
.setSmallIcon(R.drawable.ic_archive_24dp)
|
||||
.setProgress(1, 0, false);
|
||||
notification.setOngoing(true);
|
||||
notification.setLocalOnly(true);
|
||||
return notification;
|
||||
}
|
||||
|
||||
private void throwIfWorkStopped() throws WorkStoppedException {
|
||||
if (isStopped()) {
|
||||
throw new WorkStoppedException();
|
||||
}
|
||||
}
|
||||
|
||||
private void mediaScannerScanFile(final File file) {
|
||||
|
@ -313,7 +377,7 @@ public class ExportBackupWorker extends Worker {
|
|||
final String uuid,
|
||||
final JsonWriter writer,
|
||||
final Progress progress)
|
||||
throws IOException {
|
||||
throws IOException, WorkStoppedException {
|
||||
final var notificationManager =
|
||||
getApplicationContext().getSystemService(NotificationManager.class);
|
||||
try (final Cursor cursor =
|
||||
|
@ -322,9 +386,11 @@ public class ExportBackupWorker extends Worker {
|
|||
new String[] {uuid})) {
|
||||
final int size = cursor != null ? cursor.getCount() : 0;
|
||||
Log.d(Config.LOGTAG, "exporting " + size + " messages for account " + uuid);
|
||||
long lastUpdate = 0;
|
||||
int i = 0;
|
||||
int p = 0;
|
||||
int p = Integer.MIN_VALUE;
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
throwIfWorkStopped();
|
||||
writer.beginObject();
|
||||
writer.name("table");
|
||||
writer.value(Message.TABLENAME);
|
||||
|
@ -339,8 +405,9 @@ public class ExportBackupWorker extends Worker {
|
|||
writer.endObject();
|
||||
writer.endObject();
|
||||
final int percentage = i * 100 / size;
|
||||
if (p < percentage) {
|
||||
if (p < percentage && (SystemClock.elapsedRealtime() - lastUpdate) > 2_000) {
|
||||
p = percentage;
|
||||
lastUpdate = SystemClock.elapsedRealtime();
|
||||
notificationManager.notify(NOTIFICATION_ID, progress.build(p));
|
||||
}
|
||||
i++;
|
||||
|
@ -377,13 +444,7 @@ public class ExportBackupWorker extends Worker {
|
|||
final Intent chooser =
|
||||
Intent.createChooser(intent, context.getString(R.string.share_backup_files));
|
||||
final var shareFilesIntent =
|
||||
PendingIntent.getActivity(
|
||||
context,
|
||||
190,
|
||||
chooser,
|
||||
s()
|
||||
? PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
|
||||
: PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
PendingIntent.getActivity(context, 190, chooser, PENDING_INTENT_FLAGS);
|
||||
|
||||
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context, "backup");
|
||||
mBuilder.setContentTitle(context.getString(R.string.notification_backup_created_title))
|
||||
|
@ -418,14 +479,7 @@ public class ExportBackupWorker extends Worker {
|
|||
for (final Intent intent : getPossibleFileOpenIntents(context, path)) {
|
||||
if (intent.resolveActivityInfo(context.getPackageManager(), 0) != null) {
|
||||
return Optional.of(
|
||||
PendingIntent.getActivity(
|
||||
context,
|
||||
189,
|
||||
intent,
|
||||
s()
|
||||
? PendingIntent.FLAG_IMMUTABLE
|
||||
| PendingIntent.FLAG_UPDATE_CURRENT
|
||||
: PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
PendingIntent.getActivity(context, 189, intent, PENDING_INTENT_FLAGS));
|
||||
}
|
||||
}
|
||||
return Optional.absent();
|
||||
|
@ -474,4 +528,6 @@ public class ExportBackupWorker extends Worker {
|
|||
return notification.build();
|
||||
}
|
||||
}
|
||||
|
||||
private static class WorkStoppedException extends Exception {}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue