package org.thoughtcrime.securesms.jobs; import android.Manifest; import androidx.annotation.NonNull; import org.thoughtcrime.securesms.backup.BackupPassphrase; import org.thoughtcrime.securesms.backup.FullBackupExporter; import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.NoExternalStorageException; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.service.GenericForegroundService; import org.thoughtcrime.securesms.util.BackupUtilOld; import org.thoughtcrime.securesms.util.ExternalStorageUtil; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import network.loki.messenger.R; //TODO AC: Needs to be refactored to use Storage Access Framework or Media Store API. public class LocalBackupJob extends BaseJob { public static final String KEY = "LocalBackupJob"; private static final String TAG = LocalBackupJob.class.getSimpleName(); public LocalBackupJob() { this(new Job.Parameters.Builder() .setQueue("__LOCAL_BACKUP__") .setMaxInstances(1) .setMaxAttempts(3) .build()); } private LocalBackupJob(@NonNull Job.Parameters parameters) { super(parameters); } @Override public @NonNull Data serialize() { return Data.EMPTY; } @Override public @NonNull String getFactoryKey() { return KEY; } @Override public void onRun() throws NoExternalStorageException, IOException { Log.i(TAG, "Executing backup job..."); if (!Permissions.hasAll(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { throw new IOException("No external storage permission!"); } GenericForegroundService.startForegroundTask(context, context.getString(R.string.LocalBackupJob_creating_backup), NotificationChannels.BACKUPS, R.drawable.ic_launcher_foreground); // TODO: Maybe create a new backup icon like ic_signal_backup? try { String backupPassword = BackupPassphrase.get(context); File backupDirectory = ExternalStorageUtil.getBackupDir(context); String timestamp = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US).format(new Date()); String fileName = String.format("session-%s.backup", timestamp); File backupFile = new File(backupDirectory, fileName); if (backupFile.exists()) { throw new IOException("Backup file already exists?"); } if (backupPassword == null) { throw new IOException("Backup password is null"); } File tempFile = File.createTempFile("backup", "tmp", ExternalStorageUtil.getCacheDir(context)); FullBackupExporter.export(context, AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(), DatabaseFactory.getBackupDatabase(context), tempFile, backupPassword); if (!tempFile.renameTo(backupFile)) { tempFile.delete(); throw new IOException("Renaming temporary backup file failed!"); } BackupUtilOld.deleteOldBackups(context); } finally { GenericForegroundService.stopForegroundTask(context); } } @Override public boolean onShouldRetry(@NonNull Exception e) { return false; } @Override public void onCanceled() { } public static class Factory implements Job.Factory { @Override public @NonNull LocalBackupJob create(@NonNull Parameters parameters, @NonNull Data data) { return new LocalBackupJob(parameters); } } }