mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-05-04 20:30:36 +00:00
Add support for participating in Android's backup system
This adds an option for participating in Android's backup system. Two items are backed up: the ``files/aegis.json`` file and the entire ``shared_prefs`` directory. The option is disabled by default and can only be enabled if encryption is enabled as well. I tested this with Local Transport and Seedvault. To test with Local Transport, see: https://developer.android.com/guide/topics/data/testingbackup.
This commit is contained in:
parent
07c768893a
commit
f080eaa8f9
10 changed files with 199 additions and 23 deletions
|
@ -96,7 +96,7 @@ public class AegisApplication extends Application {
|
|||
}
|
||||
|
||||
if (_vaultFile == null) {
|
||||
_vaultFile = VaultManager.readFile(this);
|
||||
_vaultFile = VaultManager.readVaultFile(this);
|
||||
}
|
||||
|
||||
return _vaultFile;
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
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;
|
||||
import com.beemdevelopment.aegis.vault.VaultManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class AegisBackupAgent extends BackupAgent {
|
||||
private static final String TAG = BackupAgent.class.getSimpleName();
|
||||
|
||||
private Preferences _prefs;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
_prefs = new Preferences(this);
|
||||
}
|
||||
|
||||
@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));
|
||||
|
||||
if (!_prefs.isAndroidBackupsEnabled()) {
|
||||
Log.i(TAG, "onFullBackup() skipped: Android backups disabled in preferences");
|
||||
return;
|
||||
}
|
||||
|
||||
// first copy the vault to the files/backup directory
|
||||
createBackupDir();
|
||||
File vaultBackupFile = getVaultBackupFile();
|
||||
try (FileInputStream inStream = VaultManager.getAtomicFile(this).openRead();
|
||||
FileOutputStream outStream = new FileOutputStream(vaultBackupFile)) {
|
||||
IOUtils.copy(inStream, outStream);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, String.format("onFullBackup() failed: %s", e));
|
||||
deleteBackupDir();
|
||||
throw e;
|
||||
}
|
||||
|
||||
// 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)) {
|
||||
VaultManager.writeToFile(this, inStream);
|
||||
} 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() {
|
||||
return new File(new File(getFilesDir(), "backup"), VaultManager.FILENAME);
|
||||
}
|
||||
}
|
|
@ -164,6 +164,14 @@ public class Preferences {
|
|||
return new Locale(parts[0], parts[1]);
|
||||
}
|
||||
|
||||
public boolean isAndroidBackupsEnabled() {
|
||||
return _prefs.getBoolean("pref_android_backups", false);
|
||||
}
|
||||
|
||||
public void setIsAndroidBackupsEnabled(boolean enabled) {
|
||||
_prefs.edit().putBoolean("pref_android_backups", enabled).apply();
|
||||
}
|
||||
|
||||
public boolean isBackupsEnabled() {
|
||||
return _prefs.getBoolean("pref_backups", false);
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import com.beemdevelopment.aegis.ui.Dialogs;
|
|||
import com.beemdevelopment.aegis.vault.VaultManagerException;
|
||||
|
||||
public class BackupsPreferencesFragment extends PreferencesFragment {
|
||||
private SwitchPreferenceCompat _androidBackupsPreference;
|
||||
private SwitchPreferenceCompat _backupsPreference;
|
||||
private Preference _backupsLocationPreference;
|
||||
private Preference _backupsTriggerPreference;
|
||||
|
@ -44,6 +45,14 @@ public class BackupsPreferencesFragment extends PreferencesFragment {
|
|||
return false;
|
||||
});
|
||||
|
||||
_androidBackupsPreference = findPreference("pref_android_backups");
|
||||
_androidBackupsPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
prefs.setIsAndroidBackupsEnabled((boolean) newValue);
|
||||
updateBackupPreference();
|
||||
getVault().androidBackupDataChanged();
|
||||
return false;
|
||||
});
|
||||
|
||||
Uri backupLocation = prefs.getBackupsLocation();
|
||||
_backupsLocationPreference = findPreference("pref_backups_location");
|
||||
if (backupLocation != null) {
|
||||
|
@ -106,7 +115,10 @@ public class BackupsPreferencesFragment extends PreferencesFragment {
|
|||
|
||||
private void updateBackupPreference() {
|
||||
boolean encrypted = getVault().isEncryptionEnabled();
|
||||
boolean androidBackupEnabled = getPreferences().isAndroidBackupsEnabled() && encrypted;
|
||||
boolean backupEnabled = getPreferences().isBackupsEnabled() && encrypted;
|
||||
_androidBackupsPreference.setChecked(androidBackupEnabled);
|
||||
_androidBackupsPreference.setEnabled(encrypted);
|
||||
_backupsPreference.setChecked(backupEnabled);
|
||||
_backupsPreference.setEnabled(encrypted);
|
||||
_backupsLocationPreference.setVisible(backupEnabled);
|
||||
|
|
|
@ -95,7 +95,7 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
|||
} else {
|
||||
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
|
||||
.setTitle(R.string.disable_encryption)
|
||||
.setMessage(getString(R.string.disable_encryption_description))
|
||||
.setMessage(getText(R.string.disable_encryption_description))
|
||||
.setPositiveButton(android.R.string.yes, (dialog, which) -> {
|
||||
try {
|
||||
getVault().disableEncryption();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.beemdevelopment.aegis.vault;
|
||||
|
||||
import android.app.backup.BackupManager;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.core.util.AtomicFile;
|
||||
|
@ -10,6 +11,7 @@ import com.beemdevelopment.aegis.util.IOUtils;
|
|||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
@ -34,11 +36,13 @@ public class VaultManager {
|
|||
private Context _context;
|
||||
private Preferences _prefs;
|
||||
private VaultBackupManager _backups;
|
||||
private BackupManager _androidBackups;
|
||||
|
||||
public VaultManager(Context context, Vault vault, VaultFileCredentials creds) {
|
||||
_context = context;
|
||||
_prefs = new Preferences(context);
|
||||
_backups = new VaultBackupManager(context);
|
||||
_androidBackups = new BackupManager(context);
|
||||
_vault = vault;
|
||||
_creds = creds;
|
||||
}
|
||||
|
@ -47,7 +51,7 @@ public class VaultManager {
|
|||
this(context, vault, null);
|
||||
}
|
||||
|
||||
private static AtomicFile getAtomicFile(Context context) {
|
||||
public static AtomicFile getAtomicFile(Context context) {
|
||||
return new AtomicFile(new File(context.getFilesDir(), FILENAME));
|
||||
}
|
||||
|
||||
|
@ -60,7 +64,7 @@ public class VaultManager {
|
|||
getAtomicFile(context).delete();
|
||||
}
|
||||
|
||||
public static VaultFile readFile(Context context) throws VaultManagerException {
|
||||
public static VaultFile readVaultFile(Context context) throws VaultManagerException {
|
||||
AtomicFile file = getAtomicFile(context);
|
||||
|
||||
try {
|
||||
|
@ -71,6 +75,22 @@ public class VaultManager {
|
|||
}
|
||||
}
|
||||
|
||||
public static void writeToFile(Context context, InputStream inStream) throws IOException {
|
||||
AtomicFile file = VaultManager.getAtomicFile(context);
|
||||
|
||||
FileOutputStream outStream = null;
|
||||
try {
|
||||
outStream = file.startWrite();
|
||||
IOUtils.copy(inStream, outStream);
|
||||
file.finishWrite(outStream);
|
||||
} catch (IOException e) {
|
||||
if (outStream != null) {
|
||||
file.failWrite(outStream);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public static VaultManager init(Context context, VaultFile file, VaultFileCredentials creds) throws VaultManagerException {
|
||||
if (file.isEncrypted() && creds == null) {
|
||||
throw new IllegalArgumentException("The VaultFile is encrypted but the given VaultFileCredentials is null");
|
||||
|
@ -94,18 +114,10 @@ public class VaultManager {
|
|||
}
|
||||
|
||||
public static void save(Context context, VaultFile vaultFile) throws VaultManagerException {
|
||||
byte[] bytes = vaultFile.toBytes();
|
||||
AtomicFile file = getAtomicFile(context);
|
||||
|
||||
FileOutputStream stream = null;
|
||||
try {
|
||||
stream = file.startWrite();
|
||||
stream.write(bytes);
|
||||
file.finishWrite(stream);
|
||||
byte[] bytes = vaultFile.toBytes();
|
||||
writeToFile(context, new ByteArrayInputStream(bytes));
|
||||
} catch (IOException e) {
|
||||
if (stream != null) {
|
||||
file.failWrite(stream);
|
||||
}
|
||||
throw new VaultManagerException(e);
|
||||
}
|
||||
}
|
||||
|
@ -130,12 +142,18 @@ public class VaultManager {
|
|||
throw new VaultManagerException(e);
|
||||
}
|
||||
|
||||
if (backup && _prefs.isBackupsEnabled()) {
|
||||
try {
|
||||
backup();
|
||||
_prefs.setBackupsError(null);
|
||||
} catch (VaultManagerException e) {
|
||||
_prefs.setBackupsError(e);
|
||||
if (backup) {
|
||||
if (_prefs.isBackupsEnabled()) {
|
||||
try {
|
||||
backup();
|
||||
_prefs.setBackupsError(null);
|
||||
} catch (VaultManagerException e) {
|
||||
_prefs.setBackupsError(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (_prefs.isAndroidBackupsEnabled()) {
|
||||
androidBackupDataChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -202,6 +220,10 @@ public class VaultManager {
|
|||
}
|
||||
}
|
||||
|
||||
public void androidBackupDataChanged() {
|
||||
_androidBackups.dataChanged();
|
||||
}
|
||||
|
||||
public void addEntry(VaultEntry entry) {
|
||||
_vault.getEntries().add(entry);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue