mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-04-23 23:39:14 +00:00
Merge pull request #925 from alexbakker/intro-import
Add an import button to the intro
This commit is contained in:
commit
2dc01adc48
13 changed files with 277 additions and 24 deletions
|
@ -74,7 +74,7 @@ public abstract class AegisTest {
|
||||||
private VaultRepository initVault(@Nullable VaultFileCredentials creds, @Nullable List<VaultEntry> entries) {
|
private VaultRepository initVault(@Nullable VaultFileCredentials creds, @Nullable List<VaultEntry> entries) {
|
||||||
VaultRepository vault;
|
VaultRepository vault;
|
||||||
try {
|
try {
|
||||||
vault = _vaultManager.init(creds);
|
vault = _vaultManager.initNew(creds);
|
||||||
} catch (VaultRepositoryException e) {
|
} catch (VaultRepositoryException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,16 +6,25 @@ import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
|
||||||
import static androidx.test.espresso.action.ViewActions.replaceText;
|
import static androidx.test.espresso.action.ViewActions.replaceText;
|
||||||
import static androidx.test.espresso.action.ViewActions.typeText;
|
import static androidx.test.espresso.action.ViewActions.typeText;
|
||||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||||
|
import static androidx.test.espresso.intent.Intents.intending;
|
||||||
|
import static androidx.test.espresso.intent.matcher.IntentMatchers.isInternal;
|
||||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||||
|
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||||
import static junit.framework.TestCase.assertFalse;
|
import static junit.framework.TestCase.assertFalse;
|
||||||
import static junit.framework.TestCase.assertNull;
|
import static junit.framework.TestCase.assertNull;
|
||||||
import static junit.framework.TestCase.assertTrue;
|
import static junit.framework.TestCase.assertTrue;
|
||||||
import static org.hamcrest.Matchers.not;
|
import static org.hamcrest.Matchers.not;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Instrumentation;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
import androidx.test.espresso.IdlingRegistry;
|
import androidx.test.espresso.IdlingRegistry;
|
||||||
import androidx.test.espresso.IdlingResource;
|
import androidx.test.espresso.IdlingResource;
|
||||||
import androidx.test.espresso.ViewInteraction;
|
import androidx.test.espresso.ViewInteraction;
|
||||||
|
import androidx.test.espresso.intent.Intents;
|
||||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import androidx.test.filters.LargeTest;
|
import androidx.test.filters.LargeTest;
|
||||||
|
@ -23,6 +32,7 @@ import androidx.viewpager2.widget.ViewPager2;
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.rules.ScreenshotTestRule;
|
import com.beemdevelopment.aegis.rules.ScreenshotTestRule;
|
||||||
import com.beemdevelopment.aegis.ui.IntroActivity;
|
import com.beemdevelopment.aegis.ui.IntroActivity;
|
||||||
|
import com.beemdevelopment.aegis.util.IOUtils;
|
||||||
import com.beemdevelopment.aegis.vault.VaultRepository;
|
import com.beemdevelopment.aegis.vault.VaultRepository;
|
||||||
import com.beemdevelopment.aegis.vault.slots.BiometricSlot;
|
import com.beemdevelopment.aegis.vault.slots.BiometricSlot;
|
||||||
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
||||||
|
@ -36,6 +46,11 @@ import org.junit.rules.RuleChain;
|
||||||
import org.junit.rules.TestRule;
|
import org.junit.rules.TestRule;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
import dagger.hilt.android.testing.HiltAndroidTest;
|
import dagger.hilt.android.testing.HiltAndroidTest;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@ -51,6 +66,8 @@ public class IntroTest extends AegisTest {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
|
Intents.init();
|
||||||
|
|
||||||
_activityRule.getScenario().onActivity(activity -> {
|
_activityRule.getScenario().onActivity(activity -> {
|
||||||
_viewPager2IdlingResource = new ViewPager2IdlingResource(activity.findViewById(R.id.pager), "viewPagerIdlingResource");
|
_viewPager2IdlingResource = new ViewPager2IdlingResource(activity.findViewById(R.id.pager), "viewPagerIdlingResource");
|
||||||
IdlingRegistry.getInstance().register(_viewPager2IdlingResource);
|
IdlingRegistry.getInstance().register(_viewPager2IdlingResource);
|
||||||
|
@ -59,6 +76,7 @@ public class IntroTest extends AegisTest {
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
|
Intents.release();
|
||||||
IdlingRegistry.getInstance().unregister(_viewPager2IdlingResource);
|
IdlingRegistry.getInstance().unregister(_viewPager2IdlingResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,6 +131,58 @@ public class IntroTest extends AegisTest {
|
||||||
assertFalse(slots.has(BiometricSlot.class));
|
assertFalse(slots.has(BiometricSlot.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doIntro_Import_Plain() {
|
||||||
|
Uri uri = getResourceUri("aegis_plain.json");
|
||||||
|
Intent resultData = new Intent();
|
||||||
|
resultData.setData(uri);
|
||||||
|
|
||||||
|
Instrumentation.ActivityResult result = new Instrumentation.ActivityResult(Activity.RESULT_OK, resultData);
|
||||||
|
intending(not(isInternal())).respondWith(result);
|
||||||
|
|
||||||
|
ViewInteraction next = onView(withId(R.id.btnNext));
|
||||||
|
onView(withId(R.id.btnImport)).perform(click());
|
||||||
|
next.perform(click());
|
||||||
|
|
||||||
|
VaultRepository vault = _vaultManager.getVault();
|
||||||
|
assertFalse(vault.isEncryptionEnabled());
|
||||||
|
assertNull(vault.getCredentials());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doIntro_Import_Encrypted() {
|
||||||
|
Uri uri = getResourceUri("aegis_encrypted.json");
|
||||||
|
Intent resultData = new Intent();
|
||||||
|
resultData.setData(uri);
|
||||||
|
|
||||||
|
Instrumentation.ActivityResult result = new Instrumentation.ActivityResult(Activity.RESULT_OK, resultData);
|
||||||
|
intending(not(isInternal())).respondWith(result);
|
||||||
|
|
||||||
|
ViewInteraction next = onView(withId(R.id.btnNext));
|
||||||
|
onView(withId(R.id.btnImport)).perform(click());
|
||||||
|
onView(withId(R.id.text_input)).perform(typeText(VAULT_PASSWORD), closeSoftKeyboard());
|
||||||
|
onView(withId(android.R.id.button1)).perform(click());
|
||||||
|
next.perform(click());
|
||||||
|
|
||||||
|
VaultRepository vault = _vaultManager.getVault();
|
||||||
|
SlotList slots = vault.getCredentials().getSlots();
|
||||||
|
assertTrue(vault.isEncryptionEnabled());
|
||||||
|
assertTrue(slots.has(PasswordSlot.class));
|
||||||
|
assertFalse(slots.has(BiometricSlot.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Uri getResourceUri(String resourceName) {
|
||||||
|
File targetFile = new File(getInstrumentation().getTargetContext().getExternalCacheDir(), resourceName);
|
||||||
|
try (InputStream inStream = getClass().getResourceAsStream(resourceName);
|
||||||
|
FileOutputStream outStream = new FileOutputStream(targetFile)) {
|
||||||
|
IOUtils.copy(inStream, outStream);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Uri.fromFile(targetFile);
|
||||||
|
}
|
||||||
|
|
||||||
// Source: https://stackoverflow.com/a/32763454/12972657
|
// Source: https://stackoverflow.com/a/32763454/12972657
|
||||||
private static class ViewPager2IdlingResource implements IdlingResource {
|
private static class ViewPager2IdlingResource implements IdlingResource {
|
||||||
private final String _resName;
|
private final String _resName;
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../test/resources/com/beemdevelopment/aegis/importers/aegis_encrypted.json
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../test/resources/com/beemdevelopment/aegis/importers/aegis_plain.json
|
|
@ -3,6 +3,7 @@ package com.beemdevelopment.aegis.importers;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.lifecycle.Lifecycle;
|
import androidx.lifecycle.Lifecycle;
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.R;
|
import com.beemdevelopment.aegis.R;
|
||||||
|
@ -72,7 +73,7 @@ public class AegisImporter extends DatabaseImporter {
|
||||||
throw new DatabaseImporterException(e);
|
throw new DatabaseImporterException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new DecryptedState(obj);
|
return new DecryptedState(obj, creds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public State decrypt(char[] password) throws DatabaseImporterException {
|
public State decrypt(char[] password) throws DatabaseImporterException {
|
||||||
|
@ -109,10 +110,21 @@ public class AegisImporter extends DatabaseImporter {
|
||||||
|
|
||||||
public static class DecryptedState extends State {
|
public static class DecryptedState extends State {
|
||||||
private JSONObject _obj;
|
private JSONObject _obj;
|
||||||
|
private VaultFileCredentials _creds;
|
||||||
|
|
||||||
private DecryptedState(JSONObject obj) {
|
private DecryptedState(JSONObject obj) {
|
||||||
|
this(obj, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DecryptedState(JSONObject obj, VaultFileCredentials creds) {
|
||||||
super(false);
|
super(false);
|
||||||
_obj = obj;
|
_obj = obj;
|
||||||
|
_creds = creds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public VaultFileCredentials getCredentials() {
|
||||||
|
return _creds;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -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 static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_PASS;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.view.WindowManager;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.R;
|
import com.beemdevelopment.aegis.R;
|
||||||
|
@ -51,6 +52,18 @@ public class IntroActivity extends IntroBaseActivity {
|
||||||
return true;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,8 +71,9 @@ public class IntroActivity extends IntroBaseActivity {
|
||||||
protected void onDonePressed() {
|
protected void onDonePressed() {
|
||||||
Bundle state = getState();
|
Bundle state = getState();
|
||||||
|
|
||||||
int cryptType = state.getInt("cryptType", CRYPT_TYPE_INVALID);
|
|
||||||
VaultFileCredentials creds = (VaultFileCredentials) state.getSerializable("creds");
|
VaultFileCredentials creds = (VaultFileCredentials) state.getSerializable("creds");
|
||||||
|
if (!state.getBoolean("imported")) {
|
||||||
|
int cryptType = state.getInt("cryptType", CRYPT_TYPE_INVALID);
|
||||||
if (cryptType == CRYPT_TYPE_INVALID
|
if (cryptType == CRYPT_TYPE_INVALID
|
||||||
|| (cryptType == CRYPT_TYPE_NONE && creds != null)
|
|| (cryptType == CRYPT_TYPE_NONE && creds != null)
|
||||||
|| (cryptType == CRYPT_TYPE_PASS && (creds == null || !creds.getSlots().has(PasswordSlot.class)))
|
|| (cryptType == CRYPT_TYPE_PASS && (creds == null || !creds.getSlots().has(PasswordSlot.class)))
|
||||||
|
@ -68,12 +82,21 @@ public class IntroActivity extends IntroBaseActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
_vaultManager.init(creds);
|
_vaultManager.initNew(creds);
|
||||||
} catch (VaultRepositoryException e) {
|
} catch (VaultRepositoryException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
Dialogs.showErrorDialog(this, R.string.vault_init_error, e);
|
Dialogs.showErrorDialog(this, R.string.vault_init_error, e);
|
||||||
return;
|
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
|
// skip the intro from now on
|
||||||
_prefs.setIntroDone(true);
|
_prefs.setIntroDone(true);
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package com.beemdevelopment.aegis.ui.intro;
|
package com.beemdevelopment.aegis.ui.intro;
|
||||||
|
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
@ -13,7 +13,6 @@ import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||||
import androidx.viewpager2.widget.ViewPager2;
|
import androidx.viewpager2.widget.ViewPager2;
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.R;
|
import com.beemdevelopment.aegis.R;
|
||||||
import com.beemdevelopment.aegis.Theme;
|
|
||||||
import com.beemdevelopment.aegis.ui.AegisActivity;
|
import com.beemdevelopment.aegis.ui.AegisActivity;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
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.
|
* @param newSlide the next slide that will be shown.
|
||||||
* @return whether to block the transition.
|
* @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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +124,7 @@ public abstract class IntroBaseActivity extends AegisActivity implements IntroAc
|
||||||
* @param oldSlide the slide that was previously shown.
|
* @param oldSlide the slide that was previously shown.
|
||||||
* @param newSlide the slide that is now 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);
|
_slides.add(type);
|
||||||
_slideIndicator.setSlideCount(_slides.size());
|
_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 {
|
private class ScreenSlidePagerAdapter extends FragmentStateAdapter {
|
||||||
|
|
|
@ -1,16 +1,109 @@
|
||||||
package com.beemdevelopment.aegis.ui.slides;
|
package com.beemdevelopment.aegis.ui.slides;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.R;
|
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.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 class WelcomeSlide extends SlideFragment {
|
||||||
|
public static final int CODE_IMPORT_VAULT = 0;
|
||||||
|
|
||||||
|
private boolean _imported;
|
||||||
|
private VaultFileCredentials _creds;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ public class VaultManager {
|
||||||
|
|
||||||
if (_vaultFile != null && !_vaultFile.isEncrypted()) {
|
if (_vaultFile != null && !_vaultFile.isEncrypted()) {
|
||||||
try {
|
try {
|
||||||
load(_vaultFile, null);
|
loadFrom(_vaultFile, null);
|
||||||
} catch (VaultRepositoryException e) {
|
} catch (VaultRepositoryException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
_vaultFile = null;
|
_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).
|
* Calling this method removes the manager's internal reference to the raw vault file (if it had one).
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public VaultRepository init(@Nullable VaultFileCredentials creds) throws VaultRepositoryException {
|
public VaultRepository initNew(@Nullable VaultFileCredentials creds) throws VaultRepositoryException {
|
||||||
if (isVaultLoaded()) {
|
if (isVaultLoaded()) {
|
||||||
throw new IllegalStateException("Vault manager is already initialized");
|
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).
|
* Calling this method removes the manager's internal reference to the raw vault file (if it had one).
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@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()) {
|
if (isVaultLoaded()) {
|
||||||
throw new IllegalStateException("Vault manager is already initialized");
|
throw new IllegalStateException("Vault manager is already initialized");
|
||||||
}
|
}
|
||||||
|
@ -116,9 +116,30 @@ public class VaultManager {
|
||||||
return getVault();
|
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
|
@NonNull
|
||||||
public VaultRepository unlock(@NonNull VaultFileCredentials creds) throws VaultRepositoryException {
|
public VaultRepository unlock(@NonNull VaultFileCredentials creds) throws VaultRepositoryException {
|
||||||
VaultRepository repo = load(getVaultFile(), creds);
|
VaultRepository repo = loadFrom(getVaultFile(), creds);
|
||||||
startNotificationService();
|
startNotificationService();
|
||||||
return repo;
|
return repo;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,13 +28,27 @@
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/titleText"/>
|
app:layout_constraintTop_toBottomOf="@+id/titleText"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnImport"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="32dp"
|
||||||
|
android:text="@string/import_vault"
|
||||||
|
android:textSize="11sp"
|
||||||
|
style="?attr/introButtonStyle"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/descriptionText" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/descriptionText"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/app_description"
|
android:text="@string/app_description"
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent" />
|
app:layout_constraintBottom_toBottomOf="parent" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
<attr name="iconColorInverted" format="color" />
|
<attr name="iconColorInverted" format="color" />
|
||||||
<attr name="dropdownStyle" format="reference" />
|
<attr name="dropdownStyle" format="reference" />
|
||||||
<attr name="colorAppBar" format="color" />
|
<attr name="colorAppBar" format="color" />
|
||||||
|
<attr name="introButtonStyle" format="reference" />
|
||||||
|
|
||||||
<declare-styleable name="SlideIndicator">
|
<declare-styleable name="SlideIndicator">
|
||||||
<attr name="dot_radius" format="dimension" />
|
<attr name="dot_radius" format="dimension" />
|
||||||
|
|
|
@ -224,6 +224,7 @@
|
||||||
<item quantity="one">Imported %d entry</item>
|
<item quantity="one">Imported %d entry</item>
|
||||||
<item quantity="other">Imported %d entries</item>
|
<item quantity="other">Imported %d entries</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<string name="intro_import_error_title">An error occurred while importing the vault</string>
|
||||||
<string name="import_error_title">One or more errors occurred during the import</string>
|
<string name="import_error_title">One or more errors occurred during the import</string>
|
||||||
<string name="exporting_vault_error">An error occurred while trying to export the vault</string>
|
<string name="exporting_vault_error">An error occurred while trying to export the vault</string>
|
||||||
<string name="exported_vault">The vault has been exported</string>
|
<string name="exported_vault">The vault has been exported</string>
|
||||||
|
@ -392,6 +393,7 @@
|
||||||
<string name="pref_panic_trigger_title">Delete vault on panic trigger</string>
|
<string name="pref_panic_trigger_title">Delete vault on panic trigger</string>
|
||||||
<string name="pref_panic_trigger_summary">Delete vault when a panic trigger is received from Ripple</string>
|
<string name="pref_panic_trigger_summary">Delete vault when a panic trigger is received from Ripple</string>
|
||||||
|
|
||||||
|
<string name="import_vault">Import vault</string>
|
||||||
<string name="importer_help_2fas">Supply a 2FAS Authenticator backup file.</string>
|
<string name="importer_help_2fas">Supply a 2FAS Authenticator backup file.</string>
|
||||||
<string name="importer_help_aegis">Supply an Aegis export/backup file.</string>
|
<string name="importer_help_aegis">Supply an Aegis export/backup file.</string>
|
||||||
<string name="importer_help_authenticator_plus">Supply an Authenticator Plus export file obtained through <b>Settings -> Backup & Restore -> Export as Text and HTML</b>.</string>
|
<string name="importer_help_authenticator_plus">Supply an Authenticator Plus export file obtained through <b>Settings -> Backup & Restore -> Export as Text and HTML</b>.</string>
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
<item name="alertDialogTheme">@style/Theme.Aegis.Dialog.Light</item>
|
<item name="alertDialogTheme">@style/Theme.Aegis.Dialog.Light</item>
|
||||||
<item name="preferenceTheme">@style/Theme.Aegis.Preference.Light</item>
|
<item name="preferenceTheme">@style/Theme.Aegis.Preference.Light</item>
|
||||||
<item name="textInputStyle">@style/Widget.Aegis.TextInput.Light</item>
|
<item name="textInputStyle">@style/Widget.Aegis.TextInput.Light</item>
|
||||||
|
<item name="introButtonStyle">@style/Widget.Aegis.OutlinedButton.Light</item>
|
||||||
<item name="dropdownStyle">@style/Widget.Aegis.Dropdown.Light</item>
|
<item name="dropdownStyle">@style/Widget.Aegis.Dropdown.Light</item>
|
||||||
<item name="bottomSheetDialogTheme">@style/ThemeOverLay.Aegis.BottomSheetDialog.Rounded</item>
|
<item name="bottomSheetDialogTheme">@style/ThemeOverLay.Aegis.BottomSheetDialog.Rounded</item>
|
||||||
|
|
||||||
|
@ -74,6 +75,7 @@
|
||||||
<item name="alertDialogTheme">@style/Theme.Aegis.Dialog.Dark</item>
|
<item name="alertDialogTheme">@style/Theme.Aegis.Dialog.Dark</item>
|
||||||
<item name="preferenceTheme">@style/Theme.Aegis.Preference.Dark</item>
|
<item name="preferenceTheme">@style/Theme.Aegis.Preference.Dark</item>
|
||||||
<item name="textInputStyle">@style/Widget.Aegis.TextInput.Dark</item>
|
<item name="textInputStyle">@style/Widget.Aegis.TextInput.Dark</item>
|
||||||
|
<item name="introButtonStyle">@style/Widget.Aegis.OutlinedButton.Dark</item>
|
||||||
<item name="dropdownStyle">@style/Widget.Aegis.Dropdown.Dark</item>
|
<item name="dropdownStyle">@style/Widget.Aegis.Dropdown.Dark</item>
|
||||||
<item name="bottomSheetDialogTheme">@style/ThemeOverLay.Aegis.BottomSheetDialog.Rounded</item>
|
<item name="bottomSheetDialogTheme">@style/ThemeOverLay.Aegis.BottomSheetDialog.Rounded</item>
|
||||||
|
|
||||||
|
@ -108,6 +110,7 @@
|
||||||
<item name="alertDialogTheme">@style/Theme.Aegis.Dialog.TrueDark</item>
|
<item name="alertDialogTheme">@style/Theme.Aegis.Dialog.TrueDark</item>
|
||||||
<item name="preferenceTheme">@style/Theme.Aegis.Preference.Dark</item>
|
<item name="preferenceTheme">@style/Theme.Aegis.Preference.Dark</item>
|
||||||
<item name="textInputStyle">@style/Widget.Aegis.TextInput.Dark</item>
|
<item name="textInputStyle">@style/Widget.Aegis.TextInput.Dark</item>
|
||||||
|
<item name="introButtonStyle">@style/Widget.Aegis.OutlinedButton.Dark</item>
|
||||||
<item name="dropdownStyle">@style/Widget.Aegis.Dropdown.Dark</item>
|
<item name="dropdownStyle">@style/Widget.Aegis.Dropdown.Dark</item>
|
||||||
<item name="bottomSheetDialogTheme">@style/ThemeOverLay.Aegis.BottomSheetDialog.Rounded</item>
|
<item name="bottomSheetDialogTheme">@style/ThemeOverLay.Aegis.BottomSheetDialog.Rounded</item>
|
||||||
|
|
||||||
|
@ -224,6 +227,12 @@
|
||||||
<item name="android:textColor">@color/dialog_button_color</item>
|
<item name="android:textColor">@color/dialog_button_color</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.Aegis.OutlinedButton.Light" parent="Widget.MaterialComponents.Button.OutlinedButton"/>
|
||||||
|
<style name="Widget.Aegis.OutlinedButton.Dark" parent="Widget.MaterialComponents.Button.OutlinedButton">
|
||||||
|
<item name="android:textColor">@color/secondary_text</item>
|
||||||
|
<item name="rippleColor">@color/secondary_text</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="Widget.Aegis.BottomSheet.Rounded" parent="Widget.MaterialComponents.BottomSheet">
|
<style name="Widget.Aegis.BottomSheet.Rounded" parent="Widget.MaterialComponents.BottomSheet">
|
||||||
<item name="shapeAppearanceOverlay">@style/ShapeAppearanceOverlay.Aegis.BottomSheetDialog.Rounded</item>
|
<item name="shapeAppearanceOverlay">@style/ShapeAppearanceOverlay.Aegis.BottomSheetDialog.Rounded</item>
|
||||||
<item name="android:background">@android:color/transparent</item>
|
<item name="android:background">@android:color/transparent</item>
|
||||||
|
|
Loading…
Add table
Reference in a new issue