add ability to cancel in-progress one-off backup

This commit is contained in:
Daniel Gultsch 2024-05-03 08:51:09 +02:00
parent 45b9c4dcc9
commit 5853f57f0a
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
11 changed files with 208 additions and 111 deletions

View file

@ -143,7 +143,11 @@
</service> </service>
<receiver <receiver
android:name=".services.EventReceiver" android:name=".receiver.WorkManagerEventReceiver"
android:exported="false" />
<receiver
android:name=".receiver.SystemEventReceiver"
android:exported="false"> android:exported="false">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
@ -157,7 +161,7 @@
</receiver> </receiver>
<receiver <receiver
android:name=".services.UnifiedPushDistributor" android:name=".receiver.UnifiedPushDistributor"
android:enabled="false" android:enabled="false"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>

View file

@ -1,4 +1,4 @@
package eu.siacs.conversations.services; package eu.siacs.conversations.receiver;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
@ -10,9 +10,10 @@ import android.util.Log;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.Compatibility; 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 SETTING_ENABLED_ACCOUNTS = "enabled_accounts";
public static final String EXTRA_NEEDS_FOREGROUND_SERVICE = "needs_foreground_service"; public static final String EXTRA_NEEDS_FOREGROUND_SERVICE = "needs_foreground_service";

View file

@ -1,4 +1,4 @@
package eu.siacs.conversations.services; package eu.siacs.conversations.receiver;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
@ -25,6 +25,7 @@ import java.util.List;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.persistance.UnifiedPushDatabase; import eu.siacs.conversations.persistance.UnifiedPushDatabase;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.Compatibility; import eu.siacs.conversations.utils.Compatibility;
public class UnifiedPushDistributor extends BroadcastReceiver { public class UnifiedPushDistributor extends BroadcastReceiver {

View file

@ -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);
}
}

View file

@ -21,6 +21,7 @@ import java.util.List;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.receiver.SystemEventReceiver;
import eu.siacs.conversations.ui.ConversationsActivity; import eu.siacs.conversations.ui.ConversationsActivity;
import eu.siacs.conversations.utils.Compatibility; import eu.siacs.conversations.utils.Compatibility;
@ -44,7 +45,7 @@ public class ContactChooserTargetService extends ChooserTargetService implements
@Override @Override
public List<ChooserTarget> onGetChooserTargets( public List<ChooserTarget> onGetChooserTargets(
final ComponentName targetActivityName, final IntentFilter matchedFilter) { final ComponentName targetActivityName, final IntentFilter matchedFilter) {
if (!EventReceiver.hasEnabledAccounts(this)) { if (!SystemEventReceiver.hasEnabledAccounts(this)) {
return Collections.emptyList(); return Collections.emptyList();
} }
final Intent intent = new Intent(this, XmppConnectionService.class); final Intent intent = new Intent(this, XmppConnectionService.class);

View file

@ -28,6 +28,7 @@ import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.parser.AbstractParser; import eu.siacs.conversations.parser.AbstractParser;
import eu.siacs.conversations.persistance.UnifiedPushDatabase; import eu.siacs.conversations.persistance.UnifiedPushDatabase;
import eu.siacs.conversations.receiver.UnifiedPushDistributor;
import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;

View file

@ -128,6 +128,7 @@ import eu.siacs.conversations.parser.PresenceParser;
import eu.siacs.conversations.persistance.DatabaseBackend; import eu.siacs.conversations.persistance.DatabaseBackend;
import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.persistance.UnifiedPushDatabase; import eu.siacs.conversations.persistance.UnifiedPushDatabase;
import eu.siacs.conversations.receiver.SystemEventReceiver;
import eu.siacs.conversations.ui.ChooseAccountForProfilePictureActivity; import eu.siacs.conversations.ui.ChooseAccountForProfilePictureActivity;
import eu.siacs.conversations.ui.ConversationsActivity; import eu.siacs.conversations.ui.ConversationsActivity;
import eu.siacs.conversations.ui.RtpSessionActivity; import eu.siacs.conversations.ui.RtpSessionActivity;
@ -678,7 +679,7 @@ public class XmppConnectionService extends Service {
@Override @Override
public int onStartCommand(final Intent intent, int flags, int startId) { public int onStartCommand(final Intent intent, int flags, int startId) {
final String action = Strings.nullToEmpty(intent == null ? null : intent.getAction()); 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) { if (needsForegroundService) {
Log.d(Config.LOGTAG, "toggle forced foreground service after receiving event (action=" + action + ")"); Log.d(Config.LOGTAG, "toggle forced foreground service after receiving event (action=" + action + ")");
toggleForegroundService(true); toggleForegroundService(true);
@ -1286,7 +1287,7 @@ public class XmppConnectionService extends Service {
this.accounts = databaseBackend.getAccounts(); this.accounts = databaseBackend.getAccounts();
final SharedPreferences.Editor editor = getPreferences().edit(); final SharedPreferences.Editor editor = getPreferences().edit();
final boolean hasEnabledAccounts = hasEnabledAccounts(); final boolean hasEnabledAccounts = hasEnabledAccounts();
editor.putBoolean(EventReceiver.SETTING_ENABLED_ACCOUNTS, hasEnabledAccounts).apply(); editor.putBoolean(SystemEventReceiver.SETTING_ENABLED_ACCOUNTS, hasEnabledAccounts).apply();
editor.apply(); editor.apply();
toggleSetProfilePictureActivity(hasEnabledAccounts); toggleSetProfilePictureActivity(hasEnabledAccounts);
reconfigurePushDistributor(); reconfigurePushDistributor();
@ -1582,7 +1583,7 @@ public class XmppConnectionService extends Service {
return; return;
} }
final long triggerAtMillis = SystemClock.elapsedRealtime() + (Config.POST_CONNECTIVITY_CHANGE_PING_INTERVAL * 1000); 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); intent.setAction(ACTION_POST_CONNECTIVITY_CHANGE);
try { try {
final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 1, intent, s() final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 1, intent, s()
@ -1604,7 +1605,7 @@ public class XmppConnectionService extends Service {
if (alarmManager == null) { if (alarmManager == null) {
return; return;
} }
final Intent intent = new Intent(this, EventReceiver.class); final Intent intent = new Intent(this, SystemEventReceiver.class);
intent.setAction(ACTION_PING); intent.setAction(ACTION_PING);
try { try {
final PendingIntent pendingIntent = final PendingIntent pendingIntent =
@ -1623,7 +1624,7 @@ public class XmppConnectionService extends Service {
if (alarmManager == null) { if (alarmManager == null) {
return; return;
} }
final Intent intent = new Intent(this, EventReceiver.class); final Intent intent = new Intent(this, SystemEventReceiver.class);
intent.setAction(ACTION_IDLE_PING); intent.setAction(ACTION_IDLE_PING);
try { try {
final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, s() final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, s()
@ -2573,7 +2574,7 @@ public class XmppConnectionService extends Service {
private void syncEnabledAccountSetting() { private void syncEnabledAccountSetting() {
final boolean hasEnabledAccounts = hasEnabledAccounts(); final boolean hasEnabledAccounts = hasEnabledAccounts();
getPreferences().edit().putBoolean(EventReceiver.SETTING_ENABLED_ACCOUNTS, hasEnabledAccounts).apply(); getPreferences().edit().putBoolean(SystemEventReceiver.SETTING_ENABLED_ACCOUNTS, hasEnabledAccounts).apply();
toggleSetProfilePictureActivity(hasEnabledAccounts); toggleSetProfilePictureActivity(hasEnabledAccounts);
} }

View file

@ -36,7 +36,7 @@ import java.util.concurrent.TimeUnit;
public class BackupSettingsFragment extends XmppPreferenceFragment { 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 static final String RECURRING_BACKUP = "recurring_backup";
private final ActivityResultLauncher<String> requestStorageForBackupLauncher = private final ActivityResultLauncher<String> requestStorageForBackupLauncher =

View file

@ -13,7 +13,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
import eu.siacs.conversations.services.UnifiedPushDistributor; import eu.siacs.conversations.receiver.UnifiedPushDistributor;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import java.net.URI; import java.net.URI;

View file

@ -1,6 +1,6 @@
package eu.siacs.conversations.utils; 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.annotation.SuppressLint;
import android.app.ActivityOptions; import android.app.ActivityOptions;

View file

@ -11,6 +11,7 @@ import android.content.pm.ServiceInfo;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.net.Uri; import android.net.Uri;
import android.os.SystemClock;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -21,6 +22,7 @@ import androidx.work.WorkerParameters;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.gson.stream.JsonWriter; import com.google.gson.stream.JsonWriter;
import eu.siacs.conversations.Config; 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.entities.Message;
import eu.siacs.conversations.persistance.DatabaseBackend; import eu.siacs.conversations.persistance.DatabaseBackend;
import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.receiver.WorkManagerEventReceiver;
import eu.siacs.conversations.utils.BackupFileHeader; import eu.siacs.conversations.utils.BackupFileHeader;
import eu.siacs.conversations.utils.Compatibility; import eu.siacs.conversations.utils.Compatibility;
@ -65,7 +68,7 @@ import javax.crypto.spec.SecretKeySpec;
public class ExportBackupWorker extends Worker { public class ExportBackupWorker extends Worker {
private static final SimpleDateFormat DATE_FORMAT = 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 KEYTYPE = "AES";
public static final String CIPHERMODE = "AES/GCM/NoPadding"; 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 NOTIFICATION_ID = 19;
private static final int BACKUP_CREATED_NOTIFICATION_ID = 23; 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; private final boolean recurringBackup;
public ExportBackupWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { public ExportBackupWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
@ -99,9 +107,12 @@ public class ExportBackupWorker extends Worker {
| NoSuchProviderException e) { | NoSuchProviderException e) {
Log.d(Config.LOGTAG, "could not create backup", e); Log.d(Config.LOGTAG, "could not create backup", e);
return Result.failure(); return Result.failure();
} finally {
getApplicationContext()
.getSystemService(NotificationManager.class)
.cancel(NOTIFICATION_ID);
} }
Log.d(Config.LOGTAG, "done creating " + files.size() + " backup files"); Log.d(Config.LOGTAG, "done creating " + files.size() + " backup files");
getApplicationContext().getSystemService(NotificationManager.class).cancel(NOTIFICATION_ID);
if (files.isEmpty() || recurringBackup) { if (files.isEmpty() || recurringBackup) {
return Result.success(); return Result.success();
} }
@ -113,13 +124,7 @@ public class ExportBackupWorker extends Worker {
@Override @Override
public ForegroundInfo getForegroundInfo() { public ForegroundInfo getForegroundInfo() {
Log.d(Config.LOGTAG, "getForegroundInfo()"); Log.d(Config.LOGTAG, "getForegroundInfo()");
final var context = getApplicationContext(); final NotificationCompat.Builder notification = getNotification();
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);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
return new ForegroundInfo( return new ForegroundInfo(
NOTIFICATION_ID, NOTIFICATION_ID,
@ -144,10 +149,13 @@ public class ExportBackupWorker extends Worker {
int count = 0; int count = 0;
final int max = accounts.size(); final int max = accounts.size();
final SecureRandom secureRandom = new SecureRandom(); final ImmutableList.Builder<File> files = new ImmutableList.Builder<>();
final List<File> files = new ArrayList<>();
Log.d(Config.LOGTAG, "starting backup for " + max + " accounts"); Log.d(Config.LOGTAG, "starting backup for " + max + " accounts");
for (final Account account : 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(); final String password = account.getPassword();
if (Strings.nullToEmpty(password).trim().isEmpty()) { if (Strings.nullToEmpty(password).trim().isEmpty()) {
Log.d( Log.d(
@ -155,8 +163,49 @@ public class ExportBackupWorker extends Worker {
String.format( String.format(
"skipping backup for %s because password is empty. unable to encrypt", "skipping backup for %s because password is empty. unable to encrypt",
account.getJid().asBareJid())); account.getJid().asBareJid()));
count++;
continue; continue;
} }
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);
count++;
}
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( Log.d(
Config.LOGTAG, Config.LOGTAG,
String.format( String.format(
@ -173,20 +222,20 @@ public class ExportBackupWorker extends Worker {
System.currentTimeMillis(), System.currentTimeMillis(),
IV, IV,
salt); salt);
final NotificationCompat.Builder notification = final var notification = getNotification();
new NotificationCompat.Builder(context, "backup"); if (!recurringBackup) {
notification final var cancel = new Intent(context, WorkManagerEventReceiver.class);
.setContentTitle(context.getString(R.string.notification_create_backup_title)) cancel.setAction(WorkManagerEventReceiver.ACTION_STOP_BACKUP);
.setSmallIcon(R.drawable.ic_archive_24dp) final var cancelPendingIntent =
.setProgress(1, 0, false); 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 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);
files.add(file);
final File directory = file.getParentFile(); final File directory = file.getParentFile();
if (directory != null && directory.mkdirs()) { if (directory != null && directory.mkdirs()) {
Log.d(Config.LOGTAG, "created backup directory " + directory.getAbsolutePath()); Log.d(Config.LOGTAG, "created backup directory " + directory.getAbsolutePath());
@ -204,13 +253,11 @@ public class ExportBackupWorker extends Worker {
SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
IvParameterSpec ivSpec = new IvParameterSpec(IV); IvParameterSpec ivSpec = new IvParameterSpec(IV);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
CipherOutputStream cipherOutputStream = CipherOutputStream cipherOutputStream = new CipherOutputStream(fileOutputStream, cipher);
new CipherOutputStream(fileOutputStream, cipher);
final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(cipherOutputStream); final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(cipherOutputStream);
final JsonWriter jsonWriter = final JsonWriter jsonWriter =
new JsonWriter( new JsonWriter(new OutputStreamWriter(gzipOutputStream, StandardCharsets.UTF_8));
new OutputStreamWriter(gzipOutputStream, StandardCharsets.UTF_8));
jsonWriter.beginArray(); jsonWriter.beginArray();
final SQLiteDatabase db = database.getReadableDatabase(); final SQLiteDatabase db = database.getReadableDatabase();
final String uuid = account.getUuid(); final String uuid = account.getUuid();
@ -223,6 +270,7 @@ public class ExportBackupWorker extends Worker {
SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
SQLiteAxolotlStore.SESSION_TABLENAME, SQLiteAxolotlStore.SESSION_TABLENAME,
SQLiteAxolotlStore.IDENTITIES_TABLENAME)) { SQLiteAxolotlStore.IDENTITIES_TABLENAME)) {
throwIfWorkStopped();
simpleExport(db, table, SQLiteAxolotlStore.ACCOUNT, uuid, jsonWriter); simpleExport(db, table, SQLiteAxolotlStore.ACCOUNT, uuid, jsonWriter);
} }
jsonWriter.endArray(); jsonWriter.endArray();
@ -230,9 +278,25 @@ public class ExportBackupWorker extends Worker {
jsonWriter.close(); jsonWriter.close();
mediaScannerScanFile(file); mediaScannerScanFile(file);
Log.d(Config.LOGTAG, "written backup to " + file.getAbsoluteFile()); Log.d(Config.LOGTAG, "written backup to " + file.getAbsoluteFile());
count++;
} }
return files;
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) { private void mediaScannerScanFile(final File file) {
@ -313,7 +377,7 @@ public class ExportBackupWorker extends Worker {
final String uuid, final String uuid,
final JsonWriter writer, final JsonWriter writer,
final Progress progress) final Progress progress)
throws IOException { throws IOException, WorkStoppedException {
final var notificationManager = final var notificationManager =
getApplicationContext().getSystemService(NotificationManager.class); getApplicationContext().getSystemService(NotificationManager.class);
try (final Cursor cursor = try (final Cursor cursor =
@ -322,9 +386,11 @@ public class ExportBackupWorker extends Worker {
new String[] {uuid})) { new String[] {uuid})) {
final int size = cursor != null ? cursor.getCount() : 0; final int size = cursor != null ? cursor.getCount() : 0;
Log.d(Config.LOGTAG, "exporting " + size + " messages for account " + uuid); Log.d(Config.LOGTAG, "exporting " + size + " messages for account " + uuid);
long lastUpdate = 0;
int i = 0; int i = 0;
int p = 0; int p = Integer.MIN_VALUE;
while (cursor != null && cursor.moveToNext()) { while (cursor != null && cursor.moveToNext()) {
throwIfWorkStopped();
writer.beginObject(); writer.beginObject();
writer.name("table"); writer.name("table");
writer.value(Message.TABLENAME); writer.value(Message.TABLENAME);
@ -339,8 +405,9 @@ public class ExportBackupWorker extends Worker {
writer.endObject(); writer.endObject();
writer.endObject(); writer.endObject();
final int percentage = i * 100 / size; final int percentage = i * 100 / size;
if (p < percentage) { if (p < percentage && (SystemClock.elapsedRealtime() - lastUpdate) > 2_000) {
p = percentage; p = percentage;
lastUpdate = SystemClock.elapsedRealtime();
notificationManager.notify(NOTIFICATION_ID, progress.build(p)); notificationManager.notify(NOTIFICATION_ID, progress.build(p));
} }
i++; i++;
@ -377,13 +444,7 @@ public class ExportBackupWorker extends Worker {
final Intent chooser = final Intent chooser =
Intent.createChooser(intent, context.getString(R.string.share_backup_files)); Intent.createChooser(intent, context.getString(R.string.share_backup_files));
final var shareFilesIntent = final var shareFilesIntent =
PendingIntent.getActivity( PendingIntent.getActivity(context, 190, chooser, PENDING_INTENT_FLAGS);
context,
190,
chooser,
s()
? PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
: PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context, "backup"); NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context, "backup");
mBuilder.setContentTitle(context.getString(R.string.notification_backup_created_title)) 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)) { for (final Intent intent : getPossibleFileOpenIntents(context, path)) {
if (intent.resolveActivityInfo(context.getPackageManager(), 0) != null) { if (intent.resolveActivityInfo(context.getPackageManager(), 0) != null) {
return Optional.of( return Optional.of(
PendingIntent.getActivity( PendingIntent.getActivity(context, 189, intent, PENDING_INTENT_FLAGS));
context,
189,
intent,
s()
? PendingIntent.FLAG_IMMUTABLE
| PendingIntent.FLAG_UPDATE_CURRENT
: PendingIntent.FLAG_UPDATE_CURRENT));
} }
} }
return Optional.absent(); return Optional.absent();
@ -474,4 +528,6 @@ public class ExportBackupWorker extends Worker {
return notification.build(); return notification.build();
} }
} }
private static class WorkStoppedException extends Exception {}
} }