2021-01-03 18:19:16 +01:00
|
|
|
package com.beemdevelopment.aegis;
|
|
|
|
|
|
|
|
import android.app.backup.BackupAgent;
|
|
|
|
import android.app.backup.BackupDataInput;
|
|
|
|
import android.app.backup.BackupDataOutput;
|
|
|
|
import android.app.backup.FullBackupDataOutput;
|
|
|
|
import android.os.Build;
|
|
|
|
import android.os.ParcelFileDescriptor;
|
|
|
|
import android.util.Log;
|
|
|
|
|
|
|
|
import com.beemdevelopment.aegis.util.IOUtils;
|
2022-06-05 18:26:46 +02:00
|
|
|
import com.beemdevelopment.aegis.vault.VaultFile;
|
2022-02-06 19:00:01 +01:00
|
|
|
import com.beemdevelopment.aegis.vault.VaultRepository;
|
2022-06-05 18:26:46 +02:00
|
|
|
import com.beemdevelopment.aegis.vault.VaultRepositoryException;
|
2021-01-03 18:19:16 +01:00
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
import java.io.FileInputStream;
|
2022-06-05 18:26:46 +02:00
|
|
|
import java.io.FileOutputStream;
|
2021-01-03 18:19:16 +01:00
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStream;
|
2022-06-05 18:26:46 +02:00
|
|
|
import java.io.OutputStream;
|
2021-01-03 18:19:16 +01:00
|
|
|
|
|
|
|
public class AegisBackupAgent extends BackupAgent {
|
2022-02-17 13:04:50 +01:00
|
|
|
private static final String TAG = AegisBackupAgent.class.getSimpleName();
|
2021-01-03 18:19:16 +01:00
|
|
|
|
|
|
|
private Preferences _prefs;
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onCreate() {
|
|
|
|
super.onCreate();
|
2022-02-06 19:00:01 +01:00
|
|
|
|
2022-03-16 14:43:13 +01:00
|
|
|
// cannot use injection with Dagger Hilt here, because the app is launched in a restricted mode on restore
|
|
|
|
_prefs = new Preferences(this);
|
2021-01-03 18:19:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public synchronized void onFullBackup(FullBackupDataOutput data) throws IOException {
|
|
|
|
Log.i(TAG, String.format("onFullBackup() called: flags=%d, quota=%d",
|
|
|
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? data.getTransportFlags() : -1,
|
|
|
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? data.getQuota() : -1));
|
|
|
|
|
2022-02-17 13:04:50 +01:00
|
|
|
boolean isD2D = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
|
|
|
|
&& (data.getTransportFlags() & FLAG_DEVICE_TO_DEVICE_TRANSFER) == FLAG_DEVICE_TO_DEVICE_TRANSFER;
|
|
|
|
|
|
|
|
if (isD2D) {
|
|
|
|
Log.i(TAG, "onFullBackup(): allowing D2D transfer");
|
|
|
|
} else if (!_prefs.isAndroidBackupsEnabled()) {
|
2021-01-03 18:19:16 +01:00
|
|
|
Log.i(TAG, "onFullBackup() skipped: Android backups disabled in preferences");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// first copy the vault to the files/backup directory
|
|
|
|
File vaultBackupFile = getVaultBackupFile();
|
2022-06-05 18:26:46 +02:00
|
|
|
try (OutputStream outputStream = new FileOutputStream(vaultBackupFile)) {
|
|
|
|
createBackupDir();
|
|
|
|
|
|
|
|
VaultFile vaultFile = VaultRepository.readVaultFile(this);
|
|
|
|
byte[] bytes = vaultFile.exportable().toBytes();
|
|
|
|
outputStream.write(bytes);
|
|
|
|
} catch (VaultRepositoryException | IOException e) {
|
2021-01-03 18:19:16 +01:00
|
|
|
Log.e(TAG, String.format("onFullBackup() failed: %s", e));
|
|
|
|
deleteBackupDir();
|
2022-06-05 18:26:46 +02:00
|
|
|
throw new IOException(e);
|
2021-01-03 18:19:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// then call the original implementation so that fullBackupContent specified in AndroidManifest is read
|
|
|
|
try {
|
|
|
|
super.onFullBackup(data);
|
|
|
|
} catch (IOException e) {
|
|
|
|
Log.e(TAG, String.format("onFullBackup() failed: %s", e));
|
|
|
|
throw e;
|
|
|
|
} finally {
|
|
|
|
deleteBackupDir();
|
|
|
|
}
|
|
|
|
|
|
|
|
Log.i(TAG, "onFullBackup() finished");
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public synchronized void onRestoreFile(ParcelFileDescriptor data, long size, File destination, int type, long mode, long mtime) throws IOException {
|
|
|
|
Log.i(TAG, String.format("onRestoreFile() called: dest=%s", destination));
|
|
|
|
super.onRestoreFile(data, size, destination, type, mode, mtime);
|
|
|
|
|
|
|
|
File vaultBackupFile = getVaultBackupFile();
|
|
|
|
if (destination.getCanonicalFile().equals(vaultBackupFile.getCanonicalFile())) {
|
|
|
|
try (InputStream inStream = new FileInputStream(vaultBackupFile)) {
|
2022-02-06 19:00:01 +01:00
|
|
|
VaultRepository.writeToFile(this, inStream);
|
2021-01-03 18:19:16 +01:00
|
|
|
} catch (IOException e) {
|
|
|
|
Log.e(TAG, String.format("onRestoreFile() failed: dest=%s, error=%s", destination, e));
|
|
|
|
throw e;
|
|
|
|
} finally {
|
|
|
|
deleteBackupDir();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Log.i(TAG, String.format("onRestoreFile() finished: dest=%s", destination));
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public synchronized void onQuotaExceeded(long backupDataBytes, long quotaBytes) {
|
|
|
|
super.onQuotaExceeded(backupDataBytes, quotaBytes);
|
|
|
|
Log.e(TAG, String.format("onQuotaExceeded() called: backupDataBytes=%d, quotaBytes=%d", backupDataBytes, quotaBytes));
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) throws IOException {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) throws IOException {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
private void createBackupDir() throws IOException {
|
|
|
|
File dir = getVaultBackupFile().getParentFile();
|
|
|
|
if (!dir.exists() && !dir.mkdir()) {
|
|
|
|
throw new IOException(String.format("Unable to create backup directory: %s", dir.toString()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void deleteBackupDir() {
|
|
|
|
File dir = getVaultBackupFile().getParentFile();
|
|
|
|
IOUtils.clearDirectory(dir, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
private File getVaultBackupFile() {
|
2022-02-06 19:00:01 +01:00
|
|
|
return new File(new File(getFilesDir(), "backup"), VaultRepository.FILENAME);
|
|
|
|
}
|
2021-01-03 18:19:16 +01:00
|
|
|
}
|