Add an import button to the intro

This commit is contained in:
Alexander Bakker 2022-06-04 18:34:52 +02:00
parent dcda668671
commit 79022be3b6
13 changed files with 277 additions and 24 deletions

View file

@ -3,6 +3,7 @@ package com.beemdevelopment.aegis.importers;
import android.content.Context;
import android.content.DialogInterface;
import androidx.annotation.Nullable;
import androidx.lifecycle.Lifecycle;
import com.beemdevelopment.aegis.R;
@ -72,7 +73,7 @@ public class AegisImporter extends DatabaseImporter {
throw new DatabaseImporterException(e);
}
return new DecryptedState(obj);
return new DecryptedState(obj, creds);
}
public State decrypt(char[] password) throws DatabaseImporterException {
@ -109,10 +110,21 @@ public class AegisImporter extends DatabaseImporter {
public static class DecryptedState extends State {
private JSONObject _obj;
private VaultFileCredentials _creds;
private DecryptedState(JSONObject obj) {
this(obj, null);
}
private DecryptedState(JSONObject obj, VaultFileCredentials creds) {
super(false);
_obj = obj;
_creds = creds;
}
@Nullable
public VaultFileCredentials getCredentials() {
return _creds;
}
@Override

View file

@ -6,6 +6,7 @@ import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE
import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_PASS;
import android.os.Bundle;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import com.beemdevelopment.aegis.R;
@ -51,6 +52,18 @@ public class IntroActivity extends IntroBaseActivity {
return true;
}
if (oldSlide == WelcomeSlide.class
&& newSlide == SecurityPickerSlide.class
&& getState().getBoolean("imported")) {
skipToSlide(DoneSlide.class);
return true;
}
// on the welcome page, we don't want the keyboard to push any views up
getWindow().setSoftInputMode(newSlide == WelcomeSlide.class
? WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING
: WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
return false;
}
@ -58,21 +71,31 @@ public class IntroActivity extends IntroBaseActivity {
protected void onDonePressed() {
Bundle state = getState();
int cryptType = state.getInt("cryptType", CRYPT_TYPE_INVALID);
VaultFileCredentials creds = (VaultFileCredentials) state.getSerializable("creds");
if (cryptType == CRYPT_TYPE_INVALID
|| (cryptType == CRYPT_TYPE_NONE && creds != null)
|| (cryptType == CRYPT_TYPE_PASS && (creds == null || !creds.getSlots().has(PasswordSlot.class)))
|| (cryptType == CRYPT_TYPE_BIOMETRIC && (creds == null || !creds.getSlots().has(PasswordSlot.class) || !creds.getSlots().has(BiometricSlot.class)))) {
throw new RuntimeException(String.format("State of SecuritySetupSlide not properly propagated, cryptType: %d, creds: %s", cryptType, creds));
}
if (!state.getBoolean("imported")) {
int cryptType = state.getInt("cryptType", CRYPT_TYPE_INVALID);
if (cryptType == CRYPT_TYPE_INVALID
|| (cryptType == CRYPT_TYPE_NONE && creds != null)
|| (cryptType == CRYPT_TYPE_PASS && (creds == null || !creds.getSlots().has(PasswordSlot.class)))
|| (cryptType == CRYPT_TYPE_BIOMETRIC && (creds == null || !creds.getSlots().has(PasswordSlot.class) || !creds.getSlots().has(BiometricSlot.class)))) {
throw new RuntimeException(String.format("State of SecuritySetupSlide not properly propagated, cryptType: %d, creds: %s", cryptType, creds));
}
try {
_vaultManager.init(creds);
} catch (VaultRepositoryException e) {
e.printStackTrace();
Dialogs.showErrorDialog(this, R.string.vault_init_error, e);
return;
try {
_vaultManager.initNew(creds);
} catch (VaultRepositoryException e) {
e.printStackTrace();
Dialogs.showErrorDialog(this, R.string.vault_init_error, e);
return;
}
} else {
try {
_vaultManager.load(creds);
} catch (VaultRepositoryException e) {
e.printStackTrace();
Dialogs.showErrorDialog(this, R.string.vault_load_error, e);
return;
}
}
// skip the intro from now on

View file

@ -1,11 +1,11 @@
package com.beemdevelopment.aegis.ui.intro;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageButton;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.RecyclerView;
@ -13,7 +13,6 @@ import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.Theme;
import com.beemdevelopment.aegis.ui.AegisActivity;
import java.lang.ref.WeakReference;
@ -116,7 +115,7 @@ public abstract class IntroBaseActivity extends AegisActivity implements IntroAc
* @param newSlide the next slide that will be shown.
* @return whether to block the transition.
*/
protected boolean onBeforeSlideChanged(Class<? extends SlideFragment> oldSlide, Class<? extends SlideFragment> newSlide) {
protected boolean onBeforeSlideChanged(@Nullable Class<? extends SlideFragment> oldSlide, @NonNull Class<? extends SlideFragment> newSlide) {
return false;
}
@ -125,7 +124,7 @@ public abstract class IntroBaseActivity extends AegisActivity implements IntroAc
* @param oldSlide the slide that was previously shown.
* @param newSlide the slide that is now shown.
*/
protected void onAfterSlideChanged(Class<? extends SlideFragment> oldSlide, Class<? extends SlideFragment> newSlide) {
protected void onAfterSlideChanged(@Nullable Class<? extends SlideFragment> oldSlide, @NonNull Class<? extends SlideFragment> newSlide) {
}
@ -178,6 +177,13 @@ public abstract class IntroBaseActivity extends AegisActivity implements IntroAc
_slides.add(type);
_slideIndicator.setSlideCount(_slides.size());
// send 'slide changed' events for the first slide
if (_slides.size() == 1) {
Class<? extends SlideFragment> slide = _slides.get(0);
onBeforeSlideChanged(null, slide);
onAfterSlideChanged(null, slide);
}
}
private class ScreenSlidePagerAdapter extends FragmentStateAdapter {

View file

@ -1,16 +1,109 @@
package com.beemdevelopment.aegis.ui.slides;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.importers.AegisImporter;
import com.beemdevelopment.aegis.importers.DatabaseImporter;
import com.beemdevelopment.aegis.importers.DatabaseImporterException;
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
import com.beemdevelopment.aegis.ui.intro.SlideFragment;
import com.beemdevelopment.aegis.ui.tasks.ImportFileTask;
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
import com.beemdevelopment.aegis.vault.VaultRepository;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class WelcomeSlide extends SlideFragment {
public static final int CODE_IMPORT_VAULT = 0;
private boolean _imported;
private VaultFileCredentials _creds;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_welcome_slide, container, false);
View view = inflater.inflate(R.layout.fragment_welcome_slide, container, false);
view.findViewById(R.id.btnImport).setOnClickListener(v -> {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
startActivityForResult(intent, CODE_IMPORT_VAULT);
});
return view;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CODE_IMPORT_VAULT && data != null && data.getData() != null) {
startImportVault(data.getData());
}
}
@Override
public void onSaveIntroState(@NonNull Bundle introState) {
introState.putBoolean("imported", _imported);
introState.putSerializable("creds", _creds);
}
private void startImportVault(Uri uri) {
ImportFileTask.Params params = new ImportFileTask.Params(uri, "intro-import", null);
ImportFileTask task = new ImportFileTask(requireContext(), result -> {
if (result.getException() != null) {
Dialogs.showErrorDialog(requireContext(), R.string.reading_file_error, result.getException());
return;
}
try (FileInputStream inStream = new FileInputStream(result.getFile())) {
AegisImporter importer = new AegisImporter(requireContext());
DatabaseImporter.State state = importer.read(inStream, false);
if (state.isEncrypted()) {
state.decrypt(requireContext(), new DatabaseImporter.DecryptListener() {
@Override
protected void onStateDecrypted(DatabaseImporter.State state) {
_creds = ((AegisImporter.DecryptedState) state).getCredentials();
importVault(result.getFile());
}
@Override
protected void onError(Exception e) {
e.printStackTrace();
Dialogs.showErrorDialog(requireContext(), R.string.decryption_error, e);
}
@Override
protected void onCanceled() {
}
});
} else {
importVault(result.getFile());
}
} catch (DatabaseImporterException | IOException e) {
e.printStackTrace();
Dialogs.showErrorDialog(requireContext(), R.string.intro_import_error_title, e);
}
});
task.execute(getLifecycle(), params);
}
private void importVault(File file) {
try (FileInputStream inStream = new FileInputStream(file)) {
VaultRepository.writeToFile(requireContext(), inStream);
} catch (IOException e) {
e.printStackTrace();
Dialogs.showErrorDialog(requireContext(), R.string.intro_import_error_title, e);
return;
}
_imported = true;
goToNextSlide();
}
}

View file

@ -60,7 +60,7 @@ public class VaultManager {
if (_vaultFile != null && !_vaultFile.isEncrypted()) {
try {
load(_vaultFile, null);
loadFrom(_vaultFile, null);
} catch (VaultRepositoryException e) {
e.printStackTrace();
_vaultFile = null;
@ -76,7 +76,7 @@ public class VaultManager {
* Calling this method removes the manager's internal reference to the raw vault file (if it had one).
*/
@NonNull
public VaultRepository init(@Nullable VaultFileCredentials creds) throws VaultRepositoryException {
public VaultRepository initNew(@Nullable VaultFileCredentials creds) throws VaultRepositoryException {
if (isVaultLoaded()) {
throw new IllegalStateException("Vault manager is already initialized");
}
@ -100,7 +100,7 @@ public class VaultManager {
* Calling this method removes the manager's internal reference to the raw vault file (if it had one).
*/
@NonNull
public VaultRepository load(@NonNull VaultFile vaultFile, @Nullable VaultFileCredentials creds) throws VaultRepositoryException {
public VaultRepository loadFrom(@NonNull VaultFile vaultFile, @Nullable VaultFileCredentials creds) throws VaultRepositoryException {
if (isVaultLoaded()) {
throw new IllegalStateException("Vault manager is already initialized");
}
@ -116,9 +116,30 @@ public class VaultManager {
return getVault();
}
/**
* Initializes the vault repository by loading and decrypting the vault file stored in
* internal storage, with the given creds. It can only be called if isVaultLoaded()
* returns false.
*
* Calling this method removes the manager's internal reference to the raw vault file (if it had one).
*/
@NonNull
public VaultRepository load(@Nullable VaultFileCredentials creds) throws VaultRepositoryException {
if (isVaultLoaded()) {
throw new IllegalStateException("Vault manager is already initialized");
}
loadVaultFile();
if (isVaultLoaded()) {
return _repo;
}
return loadFrom(getVaultFile(), creds);
}
@NonNull
public VaultRepository unlock(@NonNull VaultFileCredentials creds) throws VaultRepositoryException {
VaultRepository repo = load(getVaultFile(), creds);
VaultRepository repo = loadFrom(getVaultFile(), creds);
startNotificationService();
return repo;
}