Improve backup error handling and frequency

This patch improves our backup functionality in a number of ways:
- Only backup the vault when important changes are made, not when the order of
  entries is changed, for instance.
- Don't bubble up backup errors when saving the vault.
- Instead, show an error bar in the main view if the most recent backup attempt
  failed.

<img src="https://alexbakker.me/u/kbhhj2hcgx.png" width="300" />

Clicking on the error bar will take the user to the backup settings.
This commit is contained in:
Alexander Bakker 2020-06-13 12:45:02 +02:00
parent ae5502b650
commit 08ab8237e7
11 changed files with 116 additions and 28 deletions

View file

@ -160,6 +160,14 @@ public class Preferences {
_prefs.edit().putInt("pref_backups_versions", versions).apply();
}
public void setBackupsError(Exception e) {
_prefs.edit().putString("pref_backups_error", e == null ? null : e.toString()).apply();
}
public String getBackupsError() {
return _prefs.getString("pref_backups_error", null);
}
public boolean isTimeSyncWarningEnabled() {
return _prefs.getBoolean("pref_warn_time_sync", true);
}

View file

@ -122,9 +122,9 @@ public abstract class AegisActivity extends AppCompatActivity implements AegisAp
this.getResources().updateConfiguration(config, this.getResources().getDisplayMetrics());
}
protected boolean saveVault() {
protected boolean saveVault(boolean backup) {
try {
getApp().getVaultManager().save();
getApp().getVaultManager().save(backup);
return true;
} catch (VaultManagerException e) {
Toast.makeText(this, getString(R.string.saving_error), Toast.LENGTH_LONG).show();

View file

@ -36,7 +36,6 @@ import com.beemdevelopment.aegis.helpers.UiThreadExecutor;
import com.beemdevelopment.aegis.ui.tasks.PasswordSlotDecryptTask;
import com.beemdevelopment.aegis.vault.VaultFile;
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
import com.beemdevelopment.aegis.vault.VaultManager;
import com.beemdevelopment.aegis.vault.VaultManagerException;
import com.beemdevelopment.aegis.vault.slots.BiometricSlot;
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
@ -273,10 +272,9 @@ public class AuthActivity extends AegisActivity {
} else {
try {
AegisApplication app = getApp();
VaultManager vault = app.initVaultManager(app.loadVaultFile(), creds);
app.initVaultManager(app.loadVaultFile(), creds);
if (isSlotRepaired) {
vault.setCredentials(creds);
saveVault();
saveVault(true);
}
} catch (VaultManagerException e) {
e.printStackTrace();

View file

@ -467,7 +467,7 @@ public class EditEntryActivity extends AegisActivity {
intent.putExtra("entryUUID", entry.getUUID());
intent.putExtra("delete", delete);
if (saveVault()) {
if (saveVault(true)) {
setResult(RESULT_OK, intent);
finish();
}

View file

@ -16,6 +16,8 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.SubMenu;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.appcompat.view.ActionMode;
@ -81,6 +83,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
private SearchView _searchView;
private FloatingActionsMenu _fabMenu;
private EntryListView _entryListView;
private LinearLayout _btnBackupError;
private FabScrollHelper _fabScrollHelper;
@ -125,6 +128,11 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
startScanActivity();
});
_btnBackupError = findViewById(R.id.btn_backup_error);
_btnBackupError.setOnClickListener(view -> {
startPreferencesActivity("pref_backups");
});
_fabScrollHelper = new FabScrollHelper(_fabMenu);
_selectedEntries = new ArrayList<>();
}
@ -264,7 +272,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
}
}
saveVault();
saveVault(true);
}
}
@ -400,6 +408,12 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
startActivityForResult(Intent.createChooser(chooserIntent, getString(R.string.select_picture)), CODE_SCAN_IMAGE);
}
private void startPreferencesActivity(String preference) {
Intent intent = new Intent(this, PreferencesActivity.class);
intent.putExtra("pref", preference);
startActivityForResult(intent, CODE_PREFERENCES);
}
private void doShortcutActions() {
Intent intent = getIntent();
String action = intent.getStringExtra("action");
@ -489,6 +503,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
handleDeeplink();
updateLockIcon();
doShortcutActions();
updateBackupErrorBar();
}
@Override
@ -517,7 +532,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
_entryListView.removeEntry(oldEntry);
}
saveVault();
saveVault(true);
}
@Override
@ -567,8 +582,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_settings: {
Intent intent = new Intent(this, PreferencesActivity.class);
startActivityForResult(intent, CODE_PREFERENCES);
startPreferencesActivity(null);
return true;
}
case R.id.action_about: {
@ -647,6 +661,15 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
}
}
private void updateBackupErrorBar() {
String error = null;
if (_app.getPreferences().isBackupsEnabled()) {
error = _app.getPreferences().getBackupsError();
}
_btnBackupError.setVisibility(error == null ? View.GONE : View.VISIBLE);
}
@Override
public void onEntryClick(VaultEntry entry) {
if (_actionMode != null) {
@ -694,12 +717,12 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
@Override
public void onEntryDrop(VaultEntry entry) {
saveVault();
saveVault(false);
}
@Override
public void onEntryChange(VaultEntry entry) {
saveVault();
saveVault(true);
}
public void onEntryCopy(VaultEntry entry) {

View file

@ -1,5 +1,6 @@
package com.beemdevelopment.aegis.ui;
import android.content.Intent;
import android.os.Bundle;
import android.view.MenuItem;
@ -24,6 +25,18 @@ public class PreferencesActivity extends AegisActivity {
}
}
@Override
public void onResume() {
super.onResume();
Intent intent = getIntent();
String preference = intent.getStringExtra("pref");
if (preference != null) {
_fragment.scrollToPreference(preference);
intent.removeExtra("pref");
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
// pass permission request results to the fragment

View file

@ -434,8 +434,8 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
public void onResume() {
super.onResume();
updateEncryptionPreferences();
updateBackupPreference();
}
@ -749,13 +749,14 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
_prefs.setBackupsLocation(uri);
_prefs.setIsBackupsEnabled(true);
_prefs.setBackupsError(null);
_backupsLocationPreference.setSummary(String.format("%s: %s", getString(R.string.pref_backups_location_summary), Uri.decode(uri.toString())));
updateBackupPreference();
}
private boolean saveVault() {
try {
_vault.save();
_vault.save(true);
} catch (VaultManagerException e) {
e.printStackTrace();
Dialogs.showErrorDialog(getContext(), R.string.saving_error, e);

View file

@ -96,7 +96,7 @@ public class VaultManager {
}
}
public void save() throws VaultManagerException {
public void save(boolean backup) throws VaultManagerException {
try {
JSONObject obj = _vault.toJson();
@ -108,13 +108,18 @@ public class VaultManager {
}
save(_context, file);
if (_prefs.isBackupsEnabled()) {
backup();
}
} catch (VaultFileException e) {
throw new VaultManagerException(e);
}
if (backup && _prefs.isBackupsEnabled()) {
try {
backup();
_prefs.setBackupsError(null);
} catch (VaultManagerException e) {
_prefs.setBackupsError(e);
}
}
}
public void export(OutputStream stream, boolean encrypt) throws VaultManagerException {
@ -190,11 +195,11 @@ public class VaultManager {
public void enableEncryption(VaultFileCredentials creds) throws VaultManagerException {
_creds = creds;
save();
save(true);
}
public void disableEncryption() throws VaultManagerException {
_creds = null;
save();
save(true);
}
}

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#000" android:pathData="M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z" />
</vector>

View file

@ -8,12 +8,43 @@
android:fitsSystemWindows="true"
tools:context="com.beemdevelopment.aegis.ui.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/btn_backup_error"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone"
android:clickable="true"
android:focusable="true"
android:paddingHorizontal="10dp"
android:paddingVertical="10dp"
android:background="@color/colorAccent"
android:foreground="?android:selectableItemBackground"
android:gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tint="@color/icon_primary_inverted"
android:src="@drawable/ic_alert_black_24dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/backup_error_bar_message"
android:textColor="@color/primary_text_inverted"
android:layout_marginStart="5dp" />
</LinearLayout>
<fragment
android:name="com.beemdevelopment.aegis.ui.views.EntryListView"
android:id="@+id/key_profiles"
android:layout_height="match_parent"
android:layout_height="fill_parent"
android:layout_width="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"/>
</LinearLayout>
<!-- note: the fab should always be the last element to be sure it's displayed on top -->
<com.getbase.floatingactionbutton.FloatingActionsMenu

View file

@ -248,6 +248,7 @@
<string name="google_qr_export_unrelated">Unrelated QR code found. Try restarting the scanner.</string>
<string name="google_qr_export_scanned">Scanned %d/%d QR codes</string>
<string name="google_qr_export_unexpected">Expected QR code #%d, but scanned #%d instead</string>
<string name="backup_error_bar_message"><b>Vault backup failed recently</b></string>
<string name="custom_notices_format_style" translatable="false" >
body {