Write entries to the vault directly in EditEntryActivity

This makes it so that EditEntryActivity directly saves entries to the vault,
instead of passing them back to MainActivity through an Intent first. This
prevents crashes that can occur when an entry has a large icon and the Bundle
inside the Intent becomes too large.

This is the first part of a series of patches I plan on submitting, where I try
to repair the damage done by my misguided obsession of only touching the global
state in certain places.
This commit is contained in:
Alexander Bakker 2020-05-02 18:51:33 +02:00
parent 9b328893f7
commit 301476ff5c
6 changed files with 116 additions and 84 deletions

View file

@ -8,13 +8,14 @@ import android.provider.Settings;
import android.view.WindowManager;
import android.widget.Toast;
import androidx.annotation.CallSuper;
import androidx.appcompat.app.AppCompatActivity;
import com.beemdevelopment.aegis.AegisApplication;
import com.beemdevelopment.aegis.Preferences;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.Theme;
import androidx.annotation.CallSuper;
import androidx.appcompat.app.AppCompatActivity;
import com.beemdevelopment.aegis.vault.VaultManagerException;
import java.util.Locale;
@ -126,6 +127,16 @@ public abstract class AegisActivity extends AppCompatActivity implements AegisAp
this.getResources().updateConfiguration(config, this.getResources().getDisplayMetrics());
}
protected boolean saveVault() {
try {
getApp().getVaultManager().save();
return true;
} catch (VaultManagerException e) {
Toast.makeText(this, getString(R.string.saving_error), Toast.LENGTH_LONG).show();
return false;
}
}
/**
* Reports whether this Activity has been resumed. (i.e. onResume was called)
*/

View file

@ -31,7 +31,6 @@ import androidx.appcompat.app.AlertDialog;
import com.amulyakhare.textdrawable.TextDrawable;
import com.avito.android.krop.KropView;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.beemdevelopment.aegis.encoding.Base32;
import com.beemdevelopment.aegis.encoding.EncodingException;
import com.beemdevelopment.aegis.helpers.EditTextHelper;
@ -43,16 +42,18 @@ import com.beemdevelopment.aegis.otp.OtpInfoException;
import com.beemdevelopment.aegis.otp.SteamInfo;
import com.beemdevelopment.aegis.otp.TotpInfo;
import com.beemdevelopment.aegis.util.Cloner;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.beemdevelopment.aegis.vault.VaultManager;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import java.io.ByteArrayOutputStream;
import java.text.Collator;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import de.hdodenhof.circleimageview.CircleImageView;
@ -89,22 +90,28 @@ public class EditEntryActivity extends AegisActivity {
private RelativeLayout _advancedSettingsHeader;
private RelativeLayout _advancedSettings;
private VaultManager _vault;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_entry);
_vault = getApp().getVaultManager();
_groups = _vault.getGroups();
ActionBar bar = getSupportActionBar();
bar.setHomeAsUpIndicator(R.drawable.ic_close);
bar.setDisplayHomeAsUpEnabled(true);
// retrieve info from the calling activity
Intent intent = getIntent();
_origEntry = (VaultEntry) intent.getSerializableExtra("entry");
_isNew = intent.getBooleanExtra("isNew", false);
_groups = new TreeSet<>(Collator.getInstance());
_groups.addAll(intent.getStringArrayListExtra("groups"));
if (_isNew) {
UUID entryUUID = (UUID) intent.getSerializableExtra("entryUUID");
if (entryUUID != null) {
_origEntry = _vault.getEntryByUUID(entryUUID);
} else {
_origEntry = (VaultEntry) intent.getSerializableExtra("newEntry");
_isNew = true;
setTitle(R.string.add_new_entry);
}
@ -185,9 +192,8 @@ public class EditEntryActivity extends AegisActivity {
_spinnerType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String type = _spinnerType.getSelectedItem().toString();
switch (type.toLowerCase()) {
String type = _spinnerType.getSelectedItem().toString().toLowerCase();
switch (type) {
case TotpInfo.ID:
case SteamInfo.ID:
_rowCounter.setVisibility(View.GONE);
@ -251,7 +257,6 @@ public class EditEntryActivity extends AegisActivity {
// automatically open advanced settings since 'Secret' is required.
if (_isNew) {
openAdvancedSettings();
setGroup(selectedGroup);
}
}
@ -264,6 +269,7 @@ public class EditEntryActivity extends AegisActivity {
int pos = _groups.contains(groupName) ? _groups.headSet(groupName).size() : -1;
_spinnerGroup.setSelection(pos + 1, false);
}
private void openAdvancedSettings() {
Animation fadeOut = new AlphaAnimation(1, 0);
fadeOut.setInterpolator(new AccelerateInterpolator());
@ -344,7 +350,7 @@ public class EditEntryActivity extends AegisActivity {
return;
}
finish(entry.get(), false);
addAndFinish(entry.get());
},
(dialog, which) -> super.onBackPressed()
);
@ -361,7 +367,7 @@ public class EditEntryActivity extends AegisActivity {
break;
case R.id.action_delete:
Dialogs.showDeleteEntryDialog(this, (dialog, which) -> {
finish(_origEntry, true);
deleteAndFinish(_origEntry);
});
break;
case R.id.action_default_icon:
@ -389,12 +395,30 @@ public class EditEntryActivity extends AegisActivity {
return true;
}
private void finish(VaultEntry entry, boolean delete) {
private void addAndFinish(VaultEntry entry) {
if (_isNew) {
_vault.addEntry(entry);
} else {
_vault.replaceEntry(entry);
}
saveAndFinish(entry, false);
}
private void deleteAndFinish(VaultEntry entry) {
_vault.removeEntry(entry);
saveAndFinish(entry, true);
}
private void saveAndFinish(VaultEntry entry, boolean delete) {
Intent intent = new Intent();
intent.putExtra("entry", entry);
intent.putExtra("entryUUID", entry.getUUID());
intent.putExtra("delete", delete);
setResult(RESULT_OK, intent);
finish();
if (saveVault()) {
setResult(RESULT_OK, intent);
finish();
}
}
@Override
@ -540,7 +564,7 @@ public class EditEntryActivity extends AegisActivity {
return false;
}
finish(entry, false);
addAndFinish(entry);
return true;
}

View file

@ -51,17 +51,17 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;
import java.util.UUID;
public class MainActivity extends AegisActivity implements EntryListView.Listener {
// activity request codes
private static final int CODE_SCAN = 0;
private static final int CODE_ADD_ENTRY = 1;
private static final int CODE_EDIT_ENTRY = 2;
private static final int CODE_ENTER_ENTRY = 3;
private static final int CODE_DO_INTRO = 4;
private static final int CODE_DECRYPT = 5;
private static final int CODE_PREFERENCES = 6;
private static final int CODE_SCAN_IMAGE = 7;
private static final int CODE_DO_INTRO = 3;
private static final int CODE_DECRYPT = 4;
private static final int CODE_PREFERENCES = 5;
private static final int CODE_SCAN_IMAGE = 6;
// permission request codes
private static final int CODE_PERM_CAMERA = 0;
@ -112,7 +112,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
_fabMenu = findViewById(R.id.fab);
findViewById(R.id.fab_enter).setOnClickListener(view -> {
_fabMenu.collapse();
startEditProfileActivity(CODE_ENTER_ENTRY, null, true);
startEditEntryActivity(CODE_ADD_ENTRY, null, true);
});
findViewById(R.id.fab_scan_image).setOnClickListener(view -> {
_fabMenu.collapse();
@ -179,9 +179,6 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
case CODE_EDIT_ENTRY:
onEditEntryResult(resultCode, data);
break;
case CODE_ENTER_ENTRY:
onEnterEntryResult(resultCode, data);
break;
case CODE_DO_INTRO:
onDoIntroResult(resultCode, data);
break;
@ -238,50 +235,42 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
}
}
private void startEditProfileActivity(int requestCode, VaultEntry entry, boolean isNew) {
private void startEditEntryActivity(int requestCode, VaultEntry entry, boolean isNew) {
Intent intent = new Intent(this, EditEntryActivity.class);
intent.putExtra("entry", entry != null ? entry : VaultEntry.getDefault());
intent.putExtra("isNew", isNew);
if (isNew) {
intent.putExtra("newEntry", entry != null ? entry : VaultEntry.getDefault());
} else {
intent.putExtra("entryUUID", entry.getUUID());
}
intent.putExtra("selectedGroup", _selectedGroup);
intent.putExtra("groups", new ArrayList<>(_vault.getGroups()));
startActivityForResult(intent, requestCode);
}
private void onScanResult(int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
VaultEntry entry = (VaultEntry) data.getSerializableExtra("entry");
startEditProfileActivity(CODE_ADD_ENTRY, entry, true);
startEditEntryActivity(CODE_ADD_ENTRY, entry, true);
}
}
private void onAddEntryResult(int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
VaultEntry entry = (VaultEntry) data.getSerializableExtra("entry");
addEntry(entry);
saveVault();
UUID entryUUID = (UUID) data.getSerializableExtra("entryUUID");
VaultEntry entry = _vault.getEntryByUUID(entryUUID);
_entryListView.addEntry(entry);
}
}
private void onEditEntryResult(int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
VaultEntry entry = (VaultEntry) data.getSerializableExtra("entry");
if (data.getBooleanExtra("delete", false)) {
deleteEntry(entry);
} else {
// this profile has been serialized/deserialized and is no longer the same instance it once was
// to deal with this, the replaceEntry functions are used
VaultEntry oldEntry = _vault.replaceEntry(entry);
_entryListView.replaceEntry(oldEntry, entry);
saveVault();
}
}
}
UUID entryUUID = (UUID) data.getSerializableExtra("entryUUID");
private void onEnterEntryResult(int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
VaultEntry entry = (VaultEntry) data.getSerializableExtra("entry");
addEntry(entry);
saveVault();
if (data.getBooleanExtra("delete", false)) {
_entryListView.removeEntry(entryUUID);
} else {
VaultEntry entry = _vault.getEntryByUUID(entryUUID);
_entryListView.replaceEntry(entryUUID, entry);
}
}
}
@ -309,7 +298,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
GoogleAuthInfo info = GoogleAuthInfo.parseUri(result.getText());
VaultEntry entry = new VaultEntry(info);
startEditProfileActivity(CODE_ADD_ENTRY, entry, true);
startEditEntryActivity(CODE_ADD_ENTRY, entry, true);
} catch (NotFoundException | IOException | ChecksumException | FormatException | GoogleAuthInfoException e) {
Toast.makeText(this, getString(R.string.unable_to_read_qrcode), Toast.LENGTH_SHORT).show();
e.printStackTrace();
@ -359,11 +348,6 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
_entryListView.setGroupFilter(group, true);
}
private void addEntry(VaultEntry entry) {
_vault.addEntry(entry);
_entryListView.addEntry(entry);
}
private void onDoIntroResult(int resultCode, Intent data) {
if (resultCode == IntroActivity.RESULT_EXCEPTION) {
// TODO: user feedback
@ -442,10 +426,9 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
if (info != null) {
VaultEntry entry = new VaultEntry(info);
startEditProfileActivity(CODE_ADD_ENTRY, entry, true);
startEditEntryActivity(CODE_ADD_ENTRY, entry, true);
}
}
}
@Override
@ -511,13 +494,6 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
saveVault();
}
private void deleteEntry(VaultEntry entry) {
VaultEntry oldEntry = _vault.removeEntry(entry);
saveVault();
_entryListView.removeEntry(oldEntry);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
_menu = menu;
@ -662,14 +638,6 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
startActivityForResult(intent, CODE_DECRYPT);
}
private void saveVault() {
try {
_vault.save();
} catch (VaultManagerException e) {
Toast.makeText(this, getString(R.string.saving_error), Toast.LENGTH_LONG).show();
}
}
private void updateLockIcon() {
// hide the lock icon if the vault is not unlocked
if (_menu != null && !_vault.isLocked()) {
@ -784,7 +752,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
return true;
case R.id.action_edit:
startEditProfileActivity(CODE_EDIT_ENTRY, _selectedEntries.get(0), false);
startEditEntryActivity(CODE_EDIT_ENTRY, _selectedEntries.get(0), false);
mode.finish();
return true;

View file

@ -10,12 +10,12 @@ import androidx.recyclerview.widget.RecyclerView;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.SortCategory;
import com.beemdevelopment.aegis.ViewMode;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.beemdevelopment.aegis.helpers.ItemTouchHelperAdapter;
import com.beemdevelopment.aegis.otp.HotpInfo;
import com.beemdevelopment.aegis.otp.OtpInfo;
import com.beemdevelopment.aegis.otp.OtpInfoException;
import com.beemdevelopment.aegis.otp.TotpInfo;
import com.beemdevelopment.aegis.vault.VaultEntry;
import java.util.ArrayList;
import java.util.Collections;
@ -23,6 +23,7 @@ import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements ItemTouchHelperAdapter {
private EntryListView _view;
@ -144,6 +145,11 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
checkPeriodUniformity();
}
public void removeEntry(UUID uuid) {
VaultEntry entry = getEntryByUUID(uuid);
removeEntry(entry);
}
public void clearEntries() {
_entries.clear();
_shownEntries.clear();
@ -151,7 +157,8 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
checkPeriodUniformity();
}
public void replaceEntry(VaultEntry oldEntry, VaultEntry newEntry) {
public void replaceEntry(UUID uuid, VaultEntry newEntry) {
VaultEntry oldEntry = getEntryByUUID(uuid);
_entries.set(_entries.indexOf(oldEntry), newEntry);
if (_shownEntries.contains(oldEntry)) {
@ -174,6 +181,16 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
checkPeriodUniformity();
}
private VaultEntry getEntryByUUID(UUID uuid) {
for (VaultEntry entry : _entries) {
if (entry.getUUID().equals(uuid)) {
return entry;
}
}
return null;
}
private boolean isEntryFiltered(VaultEntry entry) {
String group = entry.getGroup();
String issuer = entry.getIssuer().toLowerCase();

View file

@ -22,10 +22,10 @@ import androidx.recyclerview.widget.RecyclerView;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.SortCategory;
import com.beemdevelopment.aegis.ViewMode;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.beemdevelopment.aegis.helpers.SimpleItemTouchHelperCallback;
import com.beemdevelopment.aegis.helpers.UiRefresher;
import com.beemdevelopment.aegis.otp.TotpInfo;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.bumptech.glide.Glide;
import com.bumptech.glide.ListPreloader;
import com.bumptech.glide.RequestBuilder;
@ -35,6 +35,7 @@ import com.bumptech.glide.util.ViewPreloadSizeProvider;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
public class EntryListView extends Fragment implements EntryAdapter.Listener {
private EntryAdapter _adapter;
@ -262,12 +263,17 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
updateEmptyState();
}
public void removeEntry(UUID uuid) {
_adapter.removeEntry(uuid);
updateEmptyState();
}
public void clearEntries() {
_adapter.clearEntries();
}
public void replaceEntry(VaultEntry oldEntry, VaultEntry newEntry) {
_adapter.replaceEntry(oldEntry, newEntry);
public void replaceEntry(UUID uuid, VaultEntry newEntry) {
_adapter.replaceEntry(uuid, newEntry);
}
public void runEntriesAnimation() {

View file

@ -17,6 +17,7 @@ import java.io.OutputStream;
import java.text.Collator;
import java.util.Collection;
import java.util.TreeSet;
import java.util.UUID;
public class VaultManager {
public static final String FILENAME = "aegis.json";
@ -144,6 +145,11 @@ public class VaultManager {
_vault.getEntries().add(entry);
}
public VaultEntry getEntryByUUID(UUID uuid) {
assertState(false, true);
return _vault.getEntries().getByUUID(uuid);
}
public VaultEntry removeEntry(VaultEntry entry) {
assertState(false, true);
return _vault.getEntries().remove(entry);