mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-04-20 05:49:13 +00:00
Use Dagger Hilt for dependency injection
This gets rid of our own janky dependency injection through the AegisApplication class
This commit is contained in:
parent
927f5f2bd5
commit
71f2b54deb
42 changed files with 1157 additions and 977 deletions
|
@ -1,5 +1,6 @@
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'com.google.protobuf'
|
apply plugin: 'com.google.protobuf'
|
||||||
|
apply plugin: 'dagger.hilt.android.plugin'
|
||||||
|
|
||||||
def getCmdOutput = { cmd ->
|
def getCmdOutput = { cmd ->
|
||||||
def stdout = new ByteArrayOutputStream()
|
def stdout = new ByteArrayOutputStream()
|
||||||
|
@ -126,10 +127,12 @@ dependencies {
|
||||||
def cameraxVersion = '1.0.2'
|
def cameraxVersion = '1.0.2'
|
||||||
def glideVersion = '4.12.0'
|
def glideVersion = '4.12.0'
|
||||||
def guavaVersion = '31.0.1'
|
def guavaVersion = '31.0.1'
|
||||||
|
def hiltVersion = '2.38.1'
|
||||||
def junitVersion = '4.13.2'
|
def junitVersion = '4.13.2'
|
||||||
def libsuVersion = '3.2.1'
|
def libsuVersion = '3.2.1'
|
||||||
|
|
||||||
annotationProcessor 'androidx.annotation:annotation:1.3.0'
|
annotationProcessor 'androidx.annotation:annotation:1.3.0'
|
||||||
|
annotationProcessor "com.google.dagger:hilt-compiler:$hiltVersion"
|
||||||
annotationProcessor "com.github.bumptech.glide:compiler:${glideVersion}"
|
annotationProcessor "com.github.bumptech.glide:compiler:${glideVersion}"
|
||||||
|
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
@ -142,12 +145,13 @@ dependencies {
|
||||||
implementation "androidx.core:core:1.7.0"
|
implementation "androidx.core:core:1.7.0"
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
||||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||||
implementation "androidx.lifecycle:lifecycle-process:2.4.0"
|
implementation "androidx.lifecycle:lifecycle-process:2.4.1"
|
||||||
implementation 'androidx.preference:preference:1.2.0'
|
implementation 'androidx.preference:preference:1.2.0'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
||||||
implementation "androidx.viewpager2:viewpager2:1.0.0"
|
implementation "androidx.viewpager2:viewpager2:1.0.0"
|
||||||
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
||||||
implementation 'com.caverock:androidsvg-aar:1.4'
|
implementation 'com.caverock:androidsvg-aar:1.4'
|
||||||
|
implementation "com.google.dagger:hilt-android:$hiltVersion"
|
||||||
implementation 'com.github.avito-tech:krop:0.52'
|
implementation 'com.github.avito-tech:krop:0.52'
|
||||||
implementation "com.github.bumptech.glide:annotations:${glideVersion}"
|
implementation "com.github.bumptech.glide:annotations:${glideVersion}"
|
||||||
implementation "com.github.bumptech.glide:glide:${glideVersion}"
|
implementation "com.github.bumptech.glide:glide:${glideVersion}"
|
||||||
|
@ -170,6 +174,8 @@ dependencies {
|
||||||
implementation 'info.guardianproject.trustedintents:trustedintents:0.2'
|
implementation 'info.guardianproject.trustedintents:trustedintents:0.2'
|
||||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
|
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
|
||||||
|
|
||||||
|
androidTestAnnotationProcessor "com.google.dagger:hilt-android-compiler:$hiltVersion"
|
||||||
|
androidTestImplementation "com.google.dagger:hilt-android-testing:$hiltVersion"
|
||||||
androidTestImplementation "androidx.test:core:${androidTestVersion}"
|
androidTestImplementation "androidx.test:core:${androidTestVersion}"
|
||||||
androidTestImplementation "androidx.test:runner:${androidTestVersion}"
|
androidTestImplementation "androidx.test:runner:${androidTestVersion}"
|
||||||
androidTestImplementation "androidx.test:rules:${androidTestVersion}"
|
androidTestImplementation "androidx.test:rules:${androidTestVersion}"
|
||||||
|
|
|
@ -9,15 +9,17 @@ import androidx.test.platform.app.InstrumentationRegistry;
|
||||||
import com.beemdevelopment.aegis.crypto.CryptoUtils;
|
import com.beemdevelopment.aegis.crypto.CryptoUtils;
|
||||||
import com.beemdevelopment.aegis.crypto.SCryptParameters;
|
import com.beemdevelopment.aegis.crypto.SCryptParameters;
|
||||||
import com.beemdevelopment.aegis.otp.OtpInfo;
|
import com.beemdevelopment.aegis.otp.OtpInfo;
|
||||||
import com.beemdevelopment.aegis.vault.Vault;
|
|
||||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||||
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
|
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
|
||||||
import com.beemdevelopment.aegis.vault.VaultManager;
|
import com.beemdevelopment.aegis.vault.VaultManager;
|
||||||
import com.beemdevelopment.aegis.vault.VaultManagerException;
|
import com.beemdevelopment.aegis.vault.VaultRepository;
|
||||||
|
import com.beemdevelopment.aegis.vault.VaultRepositoryException;
|
||||||
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
||||||
import com.beemdevelopment.aegis.vault.slots.SlotException;
|
import com.beemdevelopment.aegis.vault.slots.SlotException;
|
||||||
|
|
||||||
import org.hamcrest.Matcher;
|
import org.hamcrest.Matcher;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
@ -26,28 +28,41 @@ import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
import javax.crypto.NoSuchPaddingException;
|
import javax.crypto.NoSuchPaddingException;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidRule;
|
||||||
|
|
||||||
public abstract class AegisTest {
|
public abstract class AegisTest {
|
||||||
public static final String VAULT_PASSWORD = "test";
|
public static final String VAULT_PASSWORD = "test";
|
||||||
|
|
||||||
protected AegisApplication getApp() {
|
@Rule
|
||||||
return (AegisApplication) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext();
|
public HiltAndroidRule hiltRule = new HiltAndroidRule(this);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
protected VaultManager _vaultManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
protected Preferences _prefs;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
hiltRule.inject();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected VaultManager getVault() {
|
protected AegisApplicationBase getApp() {
|
||||||
return getApp().getVaultManager();
|
return (AegisApplicationBase) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected VaultManager initVault() {
|
protected VaultRepository initVault() {
|
||||||
VaultFileCredentials creds = generateCredentials();
|
VaultFileCredentials creds = generateCredentials();
|
||||||
VaultManager vault = getApp().initVaultManager(new Vault(), creds);
|
VaultRepository vault;
|
||||||
try {
|
try {
|
||||||
vault.save(false);
|
vault = _vaultManager.init(creds);
|
||||||
} catch (VaultManagerException e) {
|
} catch (VaultRepositoryException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
getApp().getPreferences().setIntroDone(true);
|
_prefs.setIntroDone(true);
|
||||||
return vault;
|
return vault;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.beemdevelopment.aegis;
|
||||||
|
|
||||||
|
import dagger.hilt.android.testing.CustomTestApplication;
|
||||||
|
|
||||||
|
@CustomTestApplication(AegisApplicationBase.class)
|
||||||
|
public interface AegisTestApplication {
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package com.beemdevelopment.aegis;
|
package com.beemdevelopment.aegis;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
import android.app.Instrumentation;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
|
@ -14,6 +15,12 @@ public class AegisTestRunner extends AndroidJUnitRunner {
|
||||||
BuildConfig.TEST.set(true);
|
BuildConfig.TEST.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Application newApplication(ClassLoader cl, String name, Context context)
|
||||||
|
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||||
|
return Instrumentation.newApplication(AegisTestApplication_Application.class, context);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void callApplicationOnCreate(Application app) {
|
public void callApplicationOnCreate(Application app) {
|
||||||
Context context = app.getApplicationContext();
|
Context context = app.getApplicationContext();
|
||||||
|
|
|
@ -21,7 +21,10 @@ import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@HiltAndroidTest
|
||||||
@LargeTest
|
@LargeTest
|
||||||
public class DeepLinkTest extends AegisTest {
|
public class DeepLinkTest extends AegisTest {
|
||||||
@Before
|
@Before
|
||||||
|
@ -37,7 +40,7 @@ public class DeepLinkTest extends AegisTest {
|
||||||
|
|
||||||
onView(withId(R.id.action_save)).perform(click());
|
onView(withId(R.id.action_save)).perform(click());
|
||||||
|
|
||||||
VaultEntry createdEntry = (VaultEntry) getVault().getEntries().toArray()[0];
|
VaultEntry createdEntry = (VaultEntry) _vaultManager.getVault().getEntries().toArray()[0];
|
||||||
assertTrue(createdEntry.equivalates(entry));
|
assertTrue(createdEntry.equivalates(entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,5 @@
|
||||||
package com.beemdevelopment.aegis;
|
package com.beemdevelopment.aegis;
|
||||||
|
|
||||||
import androidx.test.espresso.ViewInteraction;
|
|
||||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
import androidx.test.filters.LargeTest;
|
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.ui.IntroActivity;
|
|
||||||
import com.beemdevelopment.aegis.vault.VaultManager;
|
|
||||||
import com.beemdevelopment.aegis.vault.slots.BiometricSlot;
|
|
||||||
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
|
||||||
import com.beemdevelopment.aegis.vault.slots.SlotList;
|
|
||||||
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import static androidx.test.espresso.Espresso.onView;
|
import static androidx.test.espresso.Espresso.onView;
|
||||||
import static androidx.test.espresso.action.ViewActions.click;
|
import static androidx.test.espresso.action.ViewActions.click;
|
||||||
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
|
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
|
||||||
|
@ -28,7 +13,25 @@ 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 androidx.test.espresso.ViewInteraction;
|
||||||
|
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import androidx.test.filters.LargeTest;
|
||||||
|
|
||||||
|
import com.beemdevelopment.aegis.ui.IntroActivity;
|
||||||
|
import com.beemdevelopment.aegis.vault.VaultRepository;
|
||||||
|
import com.beemdevelopment.aegis.vault.slots.BiometricSlot;
|
||||||
|
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
||||||
|
import com.beemdevelopment.aegis.vault.slots.SlotList;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@HiltAndroidTest
|
||||||
@LargeTest
|
@LargeTest
|
||||||
public class IntroTest extends AegisTest {
|
public class IntroTest extends AegisTest {
|
||||||
@Rule
|
@Rule
|
||||||
|
@ -50,9 +53,9 @@ public class IntroTest extends AegisTest {
|
||||||
next.perform(click());
|
next.perform(click());
|
||||||
next.perform(click());
|
next.perform(click());
|
||||||
|
|
||||||
VaultManager vault = getVault();
|
VaultRepository vault = _vaultManager.getVault();
|
||||||
assertFalse(vault.isEncryptionEnabled());
|
assertFalse(vault.isEncryptionEnabled());
|
||||||
assertNull(getVault().getCredentials());
|
assertNull(vault.getCredentials());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -79,8 +82,8 @@ public class IntroTest extends AegisTest {
|
||||||
next.perform(click());
|
next.perform(click());
|
||||||
next.perform(click());
|
next.perform(click());
|
||||||
|
|
||||||
VaultManager vault = getVault();
|
VaultRepository vault = _vaultManager.getVault();
|
||||||
SlotList slots = getVault().getCredentials().getSlots();
|
SlotList slots = vault.getCredentials().getSlots();
|
||||||
assertTrue(vault.isEncryptionEnabled());
|
assertTrue(vault.isEncryptionEnabled());
|
||||||
assertTrue(slots.has(PasswordSlot.class));
|
assertTrue(slots.has(PasswordSlot.class));
|
||||||
assertFalse(slots.has(BiometricSlot.class));
|
assertFalse(slots.has(BiometricSlot.class));
|
||||||
|
|
|
@ -31,7 +31,7 @@ import com.beemdevelopment.aegis.otp.TotpInfo;
|
||||||
import com.beemdevelopment.aegis.otp.YandexInfo;
|
import com.beemdevelopment.aegis.otp.YandexInfo;
|
||||||
import com.beemdevelopment.aegis.ui.MainActivity;
|
import com.beemdevelopment.aegis.ui.MainActivity;
|
||||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||||
import com.beemdevelopment.aegis.vault.VaultManager;
|
import com.beemdevelopment.aegis.vault.VaultRepository;
|
||||||
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
||||||
|
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
|
@ -42,7 +42,10 @@ import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@HiltAndroidTest
|
||||||
@LargeTest
|
@LargeTest
|
||||||
public class OverallTest extends AegisTest {
|
public class OverallTest extends AegisTest {
|
||||||
private static final String _groupName = "Test";
|
private static final String _groupName = "Test";
|
||||||
|
@ -61,7 +64,7 @@ public class OverallTest extends AegisTest {
|
||||||
next.perform(click());
|
next.perform(click());
|
||||||
onView(withId(R.id.btnNext)).perform(click());
|
onView(withId(R.id.btnNext)).perform(click());
|
||||||
|
|
||||||
VaultManager vault = getVault();
|
VaultRepository vault = _vaultManager.getVault();
|
||||||
assertTrue(vault.isEncryptionEnabled());
|
assertTrue(vault.isEncryptionEnabled());
|
||||||
assertTrue(vault.getCredentials().getSlots().has(PasswordSlot.class));
|
assertTrue(vault.getCredentials().getSlots().has(PasswordSlot.class));
|
||||||
|
|
||||||
|
@ -122,7 +125,7 @@ public class OverallTest extends AegisTest {
|
||||||
onView(withText(R.string.lock)).perform(click());
|
onView(withText(R.string.lock)).perform(click());
|
||||||
onView(withId(R.id.text_password)).perform(typeText(VAULT_PASSWORD), closeSoftKeyboard());
|
onView(withId(R.id.text_password)).perform(typeText(VAULT_PASSWORD), closeSoftKeyboard());
|
||||||
onView(withId(R.id.button_decrypt)).perform(click());
|
onView(withId(R.id.button_decrypt)).perform(click());
|
||||||
vault = getVault();
|
vault = _vaultManager.getVault();
|
||||||
|
|
||||||
openContextualActionModeOverflowMenu();
|
openContextualActionModeOverflowMenu();
|
||||||
onView(withText(R.string.action_settings)).perform(click());
|
onView(withText(R.string.action_settings)).perform(click());
|
||||||
|
|
|
@ -1,25 +1,28 @@
|
||||||
package com.beemdevelopment.aegis;
|
package com.beemdevelopment.aegis;
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import androidx.test.filters.LargeTest;
|
import androidx.test.filters.SmallTest;
|
||||||
import androidx.test.rule.ActivityTestRule;
|
import androidx.test.rule.ActivityTestRule;
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.ui.PanicResponderActivity;
|
import com.beemdevelopment.aegis.ui.PanicResponderActivity;
|
||||||
import com.beemdevelopment.aegis.vault.VaultManager;
|
import com.beemdevelopment.aegis.vault.VaultRepository;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
@LargeTest
|
@HiltAndroidTest
|
||||||
|
@SmallTest
|
||||||
public class PanicTriggerTest extends AegisTest {
|
public class PanicTriggerTest extends AegisTest {
|
||||||
@Before
|
@Before
|
||||||
public void before() {
|
public void before() {
|
||||||
|
@ -28,21 +31,25 @@ public class PanicTriggerTest extends AegisTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPanicTriggerDisabled() {
|
public void testPanicTriggerDisabled() {
|
||||||
assertFalse(getApp().getPreferences().isPanicTriggerEnabled());
|
assertFalse(_prefs.isPanicTriggerEnabled());
|
||||||
launchPanic();
|
launchPanic();
|
||||||
assertFalse(getApp().isVaultLocked());
|
assertTrue(_vaultManager.isVaultLoaded());
|
||||||
assertNotNull(getApp().getVaultManager());
|
_vaultManager.getVault();
|
||||||
assertTrue(VaultManager.fileExists(getApp()));
|
assertFalse(_vaultManager.isVaultFileLoaded());
|
||||||
|
assertNull(_vaultManager.getVaultFileError());
|
||||||
|
assertTrue(VaultRepository.fileExists(getApp()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPanicTriggerEnabled() {
|
public void testPanicTriggerEnabled() {
|
||||||
getApp().getPreferences().setIsPanicTriggerEnabled(true);
|
_prefs.setIsPanicTriggerEnabled(true);
|
||||||
assertTrue(getApp().getPreferences().isPanicTriggerEnabled());
|
assertTrue(_prefs.isPanicTriggerEnabled());
|
||||||
launchPanic();
|
launchPanic();
|
||||||
assertTrue(getApp().isVaultLocked());
|
assertFalse(_vaultManager.isVaultLoaded());
|
||||||
assertNull(getApp().getVaultManager());
|
assertThrows(IllegalStateException.class, () -> _vaultManager.getVault());
|
||||||
assertFalse(VaultManager.fileExists(getApp()));
|
assertFalse(_vaultManager.isVaultFileLoaded());
|
||||||
|
assertNull(_vaultManager.getVaultFileError());
|
||||||
|
assertFalse(VaultRepository.fileExists(getApp()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void launchPanic() {
|
private void launchPanic() {
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
package com.beemdevelopment.aegis.vault;
|
package com.beemdevelopment.aegis.vault;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import androidx.test.filters.SmallTest;
|
import androidx.test.filters.SmallTest;
|
||||||
|
|
||||||
|
@ -10,30 +16,28 @@ import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import dagger.hilt.android.testing.HiltAndroidTest;
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.junit.Assert.assertNull;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@HiltAndroidTest
|
||||||
@SmallTest
|
@SmallTest
|
||||||
public class VaultManagerTest extends AegisTest {
|
public class VaultRepositoryTest extends AegisTest {
|
||||||
@Before
|
@Before
|
||||||
public void before() {
|
public void before() {
|
||||||
initVault();
|
initVault();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testToggleEncryption() throws VaultManagerException {
|
public void testToggleEncryption() throws VaultRepositoryException {
|
||||||
getVault().disableEncryption();
|
VaultRepository vault = _vaultManager.getVault();
|
||||||
assertFalse(getVault().isEncryptionEnabled());
|
_vaultManager.disableEncryption();
|
||||||
assertNull(getVault().getCredentials());
|
assertFalse(vault.isEncryptionEnabled());
|
||||||
|
assertNull(vault.getCredentials());
|
||||||
|
|
||||||
VaultFileCredentials creds = generateCredentials();
|
VaultFileCredentials creds = generateCredentials();
|
||||||
getVault().enableEncryption(creds);
|
_vaultManager.enableEncryption(creds);
|
||||||
assertTrue(getVault().isEncryptionEnabled());
|
assertTrue(vault.isEncryptionEnabled());
|
||||||
assertNotNull(getVault().getCredentials());
|
assertNotNull(vault.getCredentials());
|
||||||
assertEquals(getVault().getCredentials().getSlots().findAll(PasswordSlot.class).size(), 1);
|
assertEquals(vault.getCredentials().getSlots().findAll(PasswordSlot.class).size(), 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,239 +1,8 @@
|
||||||
package com.beemdevelopment.aegis;
|
package com.beemdevelopment.aegis;
|
||||||
|
|
||||||
import android.app.Application;
|
import dagger.hilt.android.HiltAndroidApp;
|
||||||
import android.app.NotificationChannel;
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.content.pm.ShortcutInfo;
|
|
||||||
import android.content.pm.ShortcutManager;
|
|
||||||
import android.graphics.drawable.Icon;
|
|
||||||
import android.os.Build;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
@HiltAndroidApp
|
||||||
import androidx.annotation.RequiresApi;
|
public class AegisApplication extends AegisApplicationBase {
|
||||||
import androidx.lifecycle.Lifecycle;
|
|
||||||
import androidx.lifecycle.LifecycleEventObserver;
|
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner;
|
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.icons.IconPackManager;
|
|
||||||
import com.beemdevelopment.aegis.services.NotificationService;
|
|
||||||
import com.beemdevelopment.aegis.ui.MainActivity;
|
|
||||||
import com.beemdevelopment.aegis.util.IOUtils;
|
|
||||||
import com.beemdevelopment.aegis.vault.Vault;
|
|
||||||
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.mikepenz.iconics.Iconics;
|
|
||||||
import com.mikepenz.material_design_iconic_typeface_library.MaterialDesignIconic;
|
|
||||||
import com.topjohnwu.superuser.Shell;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class AegisApplication extends Application {
|
|
||||||
private VaultFile _vaultFile;
|
|
||||||
private VaultManager _manager;
|
|
||||||
private Preferences _prefs;
|
|
||||||
private List<LockListener> _lockListeners;
|
|
||||||
private boolean _blockAutoLock;
|
|
||||||
private IconPackManager _iconPackManager;
|
|
||||||
|
|
||||||
private static final String CODE_LOCK_STATUS_ID = "lock_status_channel";
|
|
||||||
private static final String CODE_LOCK_VAULT_ACTION = "lock_vault";
|
|
||||||
|
|
||||||
static {
|
|
||||||
// to access other app's internal storage directory, run libsu commands inside the global mount namespace
|
|
||||||
Shell.setDefaultBuilder(Shell.Builder.create().setFlags(Shell.FLAG_MOUNT_MASTER));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
super.onCreate();
|
|
||||||
_prefs = new Preferences(this);
|
|
||||||
_lockListeners = new ArrayList<>();
|
|
||||||
_iconPackManager = new IconPackManager(this);
|
|
||||||
|
|
||||||
Iconics.init(this);
|
|
||||||
Iconics.registerFont(new MaterialDesignIconic());
|
|
||||||
|
|
||||||
// listen for SCREEN_OFF events
|
|
||||||
ScreenOffReceiver receiver = new ScreenOffReceiver();
|
|
||||||
IntentFilter intentFilter = new IntentFilter();
|
|
||||||
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
|
|
||||||
intentFilter.addAction(CODE_LOCK_VAULT_ACTION);
|
|
||||||
registerReceiver(receiver, intentFilter);
|
|
||||||
|
|
||||||
// lock the app if the user moves the application to the background
|
|
||||||
ProcessLifecycleOwner.get().getLifecycle().addObserver(new AppLifecycleObserver());
|
|
||||||
|
|
||||||
// clear the cache directory on startup, to make sure no temporary vault export files remain
|
|
||||||
IOUtils.clearDirectory(getCacheDir(), false);
|
|
||||||
|
|
||||||
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
|
||||||
initAppShortcuts();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
initNotificationChannels();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isVaultLocked() {
|
|
||||||
return _manager == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the vault file from disk at the default location, stores an internal
|
|
||||||
* reference to it for future use and returns it. This must only be called before
|
|
||||||
* initVaultManager() or after lock().
|
|
||||||
*/
|
|
||||||
public VaultFile loadVaultFile() throws VaultManagerException {
|
|
||||||
if (!isVaultLocked()) {
|
|
||||||
throw new AssertionError("loadVaultFile() may only be called before initVaultManager() or after lock()");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_vaultFile == null) {
|
|
||||||
_vaultFile = VaultManager.readVaultFile(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _vaultFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the vault manager by decrypting the given vaultFile with the given
|
|
||||||
* creds. This removes the internal reference to the raw vault file.
|
|
||||||
*/
|
|
||||||
public VaultManager initVaultManager(VaultFile vaultFile, VaultFileCredentials creds) throws VaultManagerException {
|
|
||||||
_vaultFile = null;
|
|
||||||
_manager = VaultManager.init(this, vaultFile, creds);
|
|
||||||
return _manager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the vault manager with the given vault and creds. This removes the
|
|
||||||
* internal reference to the raw vault file.
|
|
||||||
*/
|
|
||||||
public VaultManager initVaultManager(Vault vault, VaultFileCredentials creds) {
|
|
||||||
_vaultFile = null;
|
|
||||||
_manager = new VaultManager(this, vault, creds);
|
|
||||||
return _manager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public VaultManager getVaultManager() {
|
|
||||||
return _manager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IconPackManager getIconPackManager() {
|
|
||||||
return _iconPackManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Preferences getPreferences() {
|
|
||||||
return _prefs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAutoLockEnabled(int autoLockType) {
|
|
||||||
return _prefs.isAutoLockTypeEnabled(autoLockType) && !isVaultLocked() && _manager.isEncryptionEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void registerLockListener(LockListener listener) {
|
|
||||||
_lockListeners.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unregisterLockListener(LockListener listener) {
|
|
||||||
_lockListeners.remove(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether to block automatic lock on minimization. This should only be called
|
|
||||||
* by activities before invoking an intent that shows a DocumentsUI, because that
|
|
||||||
* action leads AppLifecycleObserver to believe that the app has been minimized.
|
|
||||||
*/
|
|
||||||
public void setBlockAutoLock(boolean block) {
|
|
||||||
_blockAutoLock = block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Locks the vault and the app.
|
|
||||||
* @param userInitiated whether or not the user initiated the lock in MainActivity.
|
|
||||||
*/
|
|
||||||
public void lock(boolean userInitiated) {
|
|
||||||
_manager.destroy();
|
|
||||||
_manager = null;
|
|
||||||
|
|
||||||
for (LockListener listener : _lockListeners) {
|
|
||||||
listener.onLocked(userInitiated);
|
|
||||||
}
|
|
||||||
|
|
||||||
stopService(new Intent(AegisApplication.this, NotificationService.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N_MR1)
|
|
||||||
private void initAppShortcuts() {
|
|
||||||
ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
|
|
||||||
if (shortcutManager == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Intent intent = new Intent(this, MainActivity.class);
|
|
||||||
intent.putExtra("action", "scan");
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
|
||||||
intent.setAction(Intent.ACTION_MAIN);
|
|
||||||
|
|
||||||
ShortcutInfo shortcut = new ShortcutInfo.Builder(this, "shortcut_new")
|
|
||||||
.setShortLabel(getString(R.string.new_entry))
|
|
||||||
.setLongLabel(getString(R.string.add_new_entry))
|
|
||||||
.setIcon(Icon.createWithResource(this, R.drawable.ic_qr_code))
|
|
||||||
.setIntent(intent)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
shortcutManager.setDynamicShortcuts(Collections.singletonList(shortcut));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initNotificationChannels() {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
CharSequence name = getString(R.string.channel_name_lock_status);
|
|
||||||
String description = getString(R.string.channel_description_lock_status);
|
|
||||||
int importance = NotificationManager.IMPORTANCE_LOW;
|
|
||||||
|
|
||||||
NotificationChannel channel = new NotificationChannel(CODE_LOCK_STATUS_ID, name, importance);
|
|
||||||
channel.setDescription(description);
|
|
||||||
|
|
||||||
NotificationManager notificationManager = getSystemService(NotificationManager.class);
|
|
||||||
notificationManager.createNotificationChannel(channel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AppLifecycleObserver implements LifecycleEventObserver {
|
|
||||||
@Override
|
|
||||||
public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
|
|
||||||
if (event == Lifecycle.Event.ON_STOP
|
|
||||||
&& isAutoLockEnabled(Preferences.AUTO_LOCK_ON_MINIMIZE)
|
|
||||||
&& !_blockAutoLock) {
|
|
||||||
lock(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ScreenOffReceiver extends BroadcastReceiver {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
if (isAutoLockEnabled(Preferences.AUTO_LOCK_ON_DEVICE_LOCK)) {
|
|
||||||
lock(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface LockListener {
|
|
||||||
/**
|
|
||||||
* When called, the app/vault has been locked and the listener should perform its cleanup operations.
|
|
||||||
* @param userInitiated whether or not the user initiated the lock in MainActivity.
|
|
||||||
*/
|
|
||||||
void onLocked(boolean userInitiated);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,138 @@
|
||||||
|
package com.beemdevelopment.aegis;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.pm.ShortcutInfo;
|
||||||
|
import android.content.pm.ShortcutManager;
|
||||||
|
import android.graphics.drawable.Icon;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
import androidx.lifecycle.Lifecycle;
|
||||||
|
import androidx.lifecycle.LifecycleEventObserver;
|
||||||
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
import androidx.lifecycle.ProcessLifecycleOwner;
|
||||||
|
|
||||||
|
import com.beemdevelopment.aegis.ui.MainActivity;
|
||||||
|
import com.beemdevelopment.aegis.util.IOUtils;
|
||||||
|
import com.beemdevelopment.aegis.vault.VaultManager;
|
||||||
|
import com.mikepenz.iconics.Iconics;
|
||||||
|
import com.mikepenz.material_design_iconic_typeface_library.MaterialDesignIconic;
|
||||||
|
import com.topjohnwu.superuser.Shell;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import dagger.hilt.InstallIn;
|
||||||
|
import dagger.hilt.android.EarlyEntryPoint;
|
||||||
|
import dagger.hilt.android.EarlyEntryPoints;
|
||||||
|
import dagger.hilt.components.SingletonComponent;
|
||||||
|
|
||||||
|
public abstract class AegisApplicationBase extends Application {
|
||||||
|
private static final String CODE_LOCK_STATUS_ID = "lock_status_channel";
|
||||||
|
private static final String CODE_LOCK_VAULT_ACTION = "lock_vault";
|
||||||
|
|
||||||
|
private VaultManager _vaultManager;
|
||||||
|
|
||||||
|
static {
|
||||||
|
// to access other app's internal storage directory, run libsu commands inside the global mount namespace
|
||||||
|
Shell.setDefaultBuilder(Shell.Builder.create().setFlags(Shell.FLAG_MOUNT_MASTER));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
_vaultManager = EarlyEntryPoints.get(this, EntryPoint.class).getVaultManager();
|
||||||
|
|
||||||
|
Iconics.init(this);
|
||||||
|
Iconics.registerFont(new MaterialDesignIconic());
|
||||||
|
|
||||||
|
// listen for SCREEN_OFF events
|
||||||
|
ScreenOffReceiver receiver = new ScreenOffReceiver();
|
||||||
|
IntentFilter intentFilter = new IntentFilter();
|
||||||
|
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
|
||||||
|
intentFilter.addAction(CODE_LOCK_VAULT_ACTION);
|
||||||
|
registerReceiver(receiver, intentFilter);
|
||||||
|
|
||||||
|
// lock the app if the user moves the application to the background
|
||||||
|
ProcessLifecycleOwner.get().getLifecycle().addObserver(new AppLifecycleObserver());
|
||||||
|
|
||||||
|
// clear the cache directory on startup, to make sure no temporary vault export files remain
|
||||||
|
IOUtils.clearDirectory(getCacheDir(), false);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||||
|
initAppShortcuts();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
initNotificationChannels();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.N_MR1)
|
||||||
|
private void initAppShortcuts() {
|
||||||
|
ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
|
||||||
|
if (shortcutManager == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent intent = new Intent(this, MainActivity.class);
|
||||||
|
intent.putExtra("action", "scan");
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||||
|
intent.setAction(Intent.ACTION_MAIN);
|
||||||
|
|
||||||
|
ShortcutInfo shortcut = new ShortcutInfo.Builder(this, "shortcut_new")
|
||||||
|
.setShortLabel(getString(R.string.new_entry))
|
||||||
|
.setLongLabel(getString(R.string.add_new_entry))
|
||||||
|
.setIcon(Icon.createWithResource(this, R.drawable.ic_qr_code))
|
||||||
|
.setIntent(intent)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
shortcutManager.setDynamicShortcuts(Collections.singletonList(shortcut));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initNotificationChannels() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
CharSequence name = getString(R.string.channel_name_lock_status);
|
||||||
|
String description = getString(R.string.channel_description_lock_status);
|
||||||
|
int importance = NotificationManager.IMPORTANCE_LOW;
|
||||||
|
|
||||||
|
NotificationChannel channel = new NotificationChannel(CODE_LOCK_STATUS_ID, name, importance);
|
||||||
|
channel.setDescription(description);
|
||||||
|
|
||||||
|
NotificationManager notificationManager = getSystemService(NotificationManager.class);
|
||||||
|
notificationManager.createNotificationChannel(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AppLifecycleObserver implements LifecycleEventObserver {
|
||||||
|
@Override
|
||||||
|
public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
|
||||||
|
if (event == Lifecycle.Event.ON_STOP
|
||||||
|
&& _vaultManager.isAutoLockEnabled(Preferences.AUTO_LOCK_ON_MINIMIZE)
|
||||||
|
&& !_vaultManager.isAutoLockBlocked()) {
|
||||||
|
_vaultManager.lock(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ScreenOffReceiver extends BroadcastReceiver {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
if (_vaultManager.isAutoLockEnabled(Preferences.AUTO_LOCK_ON_DEVICE_LOCK)) {
|
||||||
|
_vaultManager.lock(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EarlyEntryPoint
|
||||||
|
@InstallIn(SingletonComponent.class)
|
||||||
|
interface EntryPoint {
|
||||||
|
VaultManager getVaultManager();
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,22 +10,31 @@ import android.util.Log;
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.util.IOUtils;
|
import com.beemdevelopment.aegis.util.IOUtils;
|
||||||
import com.beemdevelopment.aegis.vault.VaultManager;
|
import com.beemdevelopment.aegis.vault.VaultManager;
|
||||||
|
import com.beemdevelopment.aegis.vault.VaultRepository;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import dagger.hilt.InstallIn;
|
||||||
|
import dagger.hilt.android.EarlyEntryPoint;
|
||||||
|
import dagger.hilt.android.EarlyEntryPoints;
|
||||||
|
import dagger.hilt.components.SingletonComponent;
|
||||||
|
|
||||||
public class AegisBackupAgent extends BackupAgent {
|
public class AegisBackupAgent extends BackupAgent {
|
||||||
private static final String TAG = AegisBackupAgent.class.getSimpleName();
|
private static final String TAG = AegisBackupAgent.class.getSimpleName();
|
||||||
|
|
||||||
|
private VaultManager _vaultManager;
|
||||||
private Preferences _prefs;
|
private Preferences _prefs;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
_prefs = new Preferences(this);
|
|
||||||
|
EntryPoint entryPoint = EarlyEntryPoints.get(this, EntryPoint.class);
|
||||||
|
_vaultManager = entryPoint.getVaultManager();
|
||||||
|
_prefs = entryPoint.getPreferences();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -47,9 +56,8 @@ public class AegisBackupAgent extends BackupAgent {
|
||||||
// first copy the vault to the files/backup directory
|
// first copy the vault to the files/backup directory
|
||||||
createBackupDir();
|
createBackupDir();
|
||||||
File vaultBackupFile = getVaultBackupFile();
|
File vaultBackupFile = getVaultBackupFile();
|
||||||
try (FileInputStream inStream = VaultManager.getAtomicFile(this).openRead();
|
try {
|
||||||
FileOutputStream outStream = new FileOutputStream(vaultBackupFile)) {
|
_vaultManager.getVault().backupTo(vaultBackupFile);
|
||||||
IOUtils.copy(inStream, outStream);
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, String.format("onFullBackup() failed: %s", e));
|
Log.e(TAG, String.format("onFullBackup() failed: %s", e));
|
||||||
deleteBackupDir();
|
deleteBackupDir();
|
||||||
|
@ -77,7 +85,7 @@ public class AegisBackupAgent extends BackupAgent {
|
||||||
File vaultBackupFile = getVaultBackupFile();
|
File vaultBackupFile = getVaultBackupFile();
|
||||||
if (destination.getCanonicalFile().equals(vaultBackupFile.getCanonicalFile())) {
|
if (destination.getCanonicalFile().equals(vaultBackupFile.getCanonicalFile())) {
|
||||||
try (InputStream inStream = new FileInputStream(vaultBackupFile)) {
|
try (InputStream inStream = new FileInputStream(vaultBackupFile)) {
|
||||||
VaultManager.writeToFile(this, inStream);
|
VaultRepository.writeToFile(this, inStream);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, String.format("onRestoreFile() failed: dest=%s, error=%s", destination, e));
|
Log.e(TAG, String.format("onRestoreFile() failed: dest=%s, error=%s", destination, e));
|
||||||
throw e;
|
throw e;
|
||||||
|
@ -118,6 +126,13 @@ public class AegisBackupAgent extends BackupAgent {
|
||||||
}
|
}
|
||||||
|
|
||||||
private File getVaultBackupFile() {
|
private File getVaultBackupFile() {
|
||||||
return new File(new File(getFilesDir(), "backup"), VaultManager.FILENAME);
|
return new File(new File(getFilesDir(), "backup"), VaultRepository.FILENAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EarlyEntryPoint
|
||||||
|
@InstallIn(SingletonComponent.class)
|
||||||
|
interface EntryPoint {
|
||||||
|
Preferences getPreferences();
|
||||||
|
VaultManager getVaultManager();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
35
app/src/main/java/com/beemdevelopment/aegis/AegisModule.java
Normal file
35
app/src/main/java/com/beemdevelopment/aegis/AegisModule.java
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package com.beemdevelopment.aegis;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.beemdevelopment.aegis.icons.IconPackManager;
|
||||||
|
import com.beemdevelopment.aegis.vault.VaultManager;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
import dagger.hilt.InstallIn;
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext;
|
||||||
|
import dagger.hilt.components.SingletonComponent;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent.class)
|
||||||
|
public class AegisModule {
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
public static IconPackManager provideIconPackManager(@ApplicationContext Context context) {
|
||||||
|
return new IconPackManager(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
public static VaultManager provideVaultManager(@ApplicationContext Context context) {
|
||||||
|
return new VaultManager(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
public static Preferences providePreferences(@ApplicationContext Context context) {
|
||||||
|
return new Preferences(context);
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,8 +41,11 @@ public class AboutActivity extends AegisActivity {
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
LayoutInflaterCompat.setFactory2(getLayoutInflater(), new IconicsLayoutInflater2(getDelegate()));
|
LayoutInflaterCompat.setFactory2(getLayoutInflater(), new IconicsLayoutInflater2(getDelegate()));
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
if (abortIfOrphan(savedInstanceState)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setContentView(R.layout.activity_about);
|
setContentView(R.layout.activity_about);
|
||||||
setSupportActionBar(findViewById(R.id.toolbar));
|
setSupportActionBar(findViewById(R.id.toolbar));
|
||||||
|
|
||||||
|
@ -124,7 +127,7 @@ public class AboutActivity extends AegisActivity {
|
||||||
mailIntent.putExtra(Intent.EXTRA_EMAIL, mailaddress);
|
mailIntent.putExtra(Intent.EXTRA_EMAIL, mailaddress);
|
||||||
mailIntent.putExtra(Intent.EXTRA_SUBJECT, R.string.app_name_full);
|
mailIntent.putExtra(Intent.EXTRA_SUBJECT, R.string.app_name_full);
|
||||||
|
|
||||||
startActivity(Intent.createChooser(mailIntent, this.getString(R.string.email)));
|
startActivity(Intent.createChooser(mailIntent, getString(R.string.email)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showThirdPartyLicenseDialog() {
|
private void showThirdPartyLicenseDialog() {
|
||||||
|
|
|
@ -2,7 +2,6 @@ package com.beemdevelopment.aegis.ui;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.ActivityNotFoundException;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
@ -10,58 +9,60 @@ import android.view.WindowManager;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.CallSuper;
|
import androidx.annotation.CallSuper;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.AegisApplication;
|
|
||||||
import com.beemdevelopment.aegis.Preferences;
|
import com.beemdevelopment.aegis.Preferences;
|
||||||
import com.beemdevelopment.aegis.R;
|
import com.beemdevelopment.aegis.R;
|
||||||
import com.beemdevelopment.aegis.Theme;
|
import com.beemdevelopment.aegis.Theme;
|
||||||
import com.beemdevelopment.aegis.ThemeMap;
|
import com.beemdevelopment.aegis.ThemeMap;
|
||||||
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
import com.beemdevelopment.aegis.icons.IconPackManager;
|
||||||
import com.beemdevelopment.aegis.vault.VaultManagerException;
|
import com.beemdevelopment.aegis.vault.VaultManager;
|
||||||
|
import com.beemdevelopment.aegis.vault.VaultRepositoryException;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public abstract class AegisActivity extends AppCompatActivity implements AegisApplication.LockListener {
|
import javax.inject.Inject;
|
||||||
private AegisApplication _app;
|
|
||||||
|
import dagger.hilt.InstallIn;
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
import dagger.hilt.android.EarlyEntryPoint;
|
||||||
|
import dagger.hilt.android.EarlyEntryPoints;
|
||||||
|
import dagger.hilt.components.SingletonComponent;
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
public abstract class AegisActivity extends AppCompatActivity implements VaultManager.LockListener {
|
||||||
|
protected Preferences _prefs;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
protected VaultManager _vaultManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
protected IconPackManager _iconPackManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
_app = (AegisApplication) getApplication();
|
|
||||||
|
|
||||||
// set the theme and locale before creating the activity
|
// set the theme and locale before creating the activity
|
||||||
Preferences prefs = getPreferences();
|
_prefs = EarlyEntryPoints.get(this, PrefEntryPoint.class).getPreferences();
|
||||||
onSetTheme();
|
onSetTheme();
|
||||||
setLocale(prefs.getLocale());
|
setLocale(_prefs.getLocale());
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
// if the app was killed, relaunch MainActivity and close everything else
|
|
||||||
if (savedInstanceState != null && isOrphan()) {
|
|
||||||
Intent intent = new Intent(this, MainActivity.class);
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
|
||||||
startActivity(intent);
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set FLAG_SECURE on the window of every AegisActivity
|
// set FLAG_SECURE on the window of every AegisActivity
|
||||||
if (getPreferences().isSecureScreenEnabled()) {
|
if (_prefs.isSecureScreenEnabled()) {
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// register a callback to listen for lock events
|
// register a callback to listen for lock events
|
||||||
_app.registerLockListener(this);
|
_vaultManager.registerLockListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@CallSuper
|
@CallSuper
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
_app.unregisterLockListener(this);
|
_vaultManager.unregisterLockListener(this);
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +70,7 @@ public abstract class AegisActivity extends AppCompatActivity implements AegisAp
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
_app.setBlockAutoLock(false);
|
_vaultManager.setBlockAutoLock(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("SoonBlockedPrivateApi")
|
@SuppressLint("SoonBlockedPrivateApi")
|
||||||
|
@ -86,14 +87,6 @@ public abstract class AegisActivity extends AppCompatActivity implements AegisAp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AegisApplication getApp() {
|
|
||||||
return _app;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Preferences getPreferences() {
|
|
||||||
return _app.getPreferences();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the activity is expected to set its theme.
|
* Called when the activity is expected to set its theme.
|
||||||
*/
|
*/
|
||||||
|
@ -111,7 +104,7 @@ public abstract class AegisActivity extends AppCompatActivity implements AegisAp
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Theme getConfiguredTheme() {
|
protected Theme getConfiguredTheme() {
|
||||||
Theme theme = getPreferences().getCurrentTheme();
|
Theme theme = _prefs.getCurrentTheme();
|
||||||
|
|
||||||
if (theme == Theme.SYSTEM || theme == Theme.SYSTEM_AMOLED) {
|
if (theme == Theme.SYSTEM || theme == Theme.SYSTEM_AMOLED) {
|
||||||
int currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
|
int currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
|
||||||
|
@ -131,87 +124,60 @@ public abstract class AegisActivity extends AppCompatActivity implements AegisAp
|
||||||
Configuration config = new Configuration();
|
Configuration config = new Configuration();
|
||||||
config.locale = locale;
|
config.locale = locale;
|
||||||
|
|
||||||
this.getResources().updateConfiguration(config, this.getResources().getDisplayMetrics());
|
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean saveVault(boolean backup) {
|
protected boolean saveVault() {
|
||||||
try {
|
try {
|
||||||
getApp().getVaultManager().save(backup);
|
_vaultManager.save();
|
||||||
return true;
|
return true;
|
||||||
} catch (VaultManagerException e) {
|
} catch (VaultRepositoryException e) {
|
||||||
|
Toast.makeText(this, getString(R.string.saving_error), Toast.LENGTH_LONG).show();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean saveAndBackupVault() {
|
||||||
|
try {
|
||||||
|
_vaultManager.saveAndBackup();
|
||||||
|
return true;
|
||||||
|
} catch (VaultRepositoryException e) {
|
||||||
Toast.makeText(this, getString(R.string.saving_error), Toast.LENGTH_LONG).show();
|
Toast.makeText(this, getString(R.string.saving_error), Toast.LENGTH_LONG).show();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes this activity if it has become an orphan (isOrphan() == true) and launches MainActivity.
|
||||||
|
* @param savedInstanceState the bundle passed to onCreate.
|
||||||
|
* @return whether to abort onCreate.
|
||||||
|
*/
|
||||||
|
protected boolean abortIfOrphan(Bundle savedInstanceState) {
|
||||||
|
if (savedInstanceState == null || !isOrphan()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent intent = new Intent(this, MainActivity.class);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||||
|
startActivity(intent);
|
||||||
|
finish();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reports whether this Activity instance has become an orphan. This can happen if
|
* Reports whether this Activity instance has become an orphan. This can happen if
|
||||||
* the vault was locked by an external trigger while the Activity was still open.
|
* the vault was killed/locked by an external trigger while the Activity was still open.
|
||||||
*/
|
*/
|
||||||
protected boolean isOrphan() {
|
private boolean isOrphan() {
|
||||||
return !(this instanceof MainActivity) && !(this instanceof AuthActivity) && !(this instanceof IntroActivity) && _app.isVaultLocked();
|
return !(this instanceof MainActivity)
|
||||||
|
&& !(this instanceof AuthActivity)
|
||||||
|
&& !(this instanceof IntroActivity)
|
||||||
|
&& !_vaultManager.isVaultLoaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Helper {
|
@EarlyEntryPoint
|
||||||
private Helper() {
|
@InstallIn(SingletonComponent.class)
|
||||||
|
public interface PrefEntryPoint {
|
||||||
}
|
Preferences getPreferences();
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts an external activity, temporarily blocks automatic lock of Aegis and
|
|
||||||
* shows an error dialog if the target activity is not found.
|
|
||||||
*/
|
|
||||||
public static void startExtActivityForResult(Activity activity, Intent intent, int requestCode) {
|
|
||||||
AegisApplication app = (AegisApplication) activity.getApplication();
|
|
||||||
app.setBlockAutoLock(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
activity.startActivityForResult(intent, requestCode, null);
|
|
||||||
} catch (ActivityNotFoundException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
|
|
||||||
if (isDocsAction(intent.getAction())) {
|
|
||||||
Dialogs.showErrorDialog(activity, R.string.documentsui_error, e);
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts an external activity, temporarily blocks automatic lock of Aegis and
|
|
||||||
* shows an error dialog if the target activity is not found.
|
|
||||||
*/
|
|
||||||
public static void startExtActivity(Fragment fragment, Intent intent) {
|
|
||||||
startExtActivityForResult(fragment, intent, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts an external activity, temporarily blocks automatic lock of Aegis and
|
|
||||||
* shows an error dialog if the target activity is not found.
|
|
||||||
*/
|
|
||||||
public static void startExtActivityForResult(Fragment fragment, Intent intent, int requestCode) {
|
|
||||||
AegisApplication app = (AegisApplication) fragment.getActivity().getApplication();
|
|
||||||
app.setBlockAutoLock(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
fragment.startActivityForResult(intent, requestCode, null);
|
|
||||||
} catch (ActivityNotFoundException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
|
|
||||||
if (isDocsAction(intent.getAction())) {
|
|
||||||
Dialogs.showErrorDialog(fragment.getContext(), R.string.documentsui_error, e);
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isDocsAction(@Nullable String action) {
|
|
||||||
return action != null && (action.equals(Intent.ACTION_GET_CONTENT)
|
|
||||||
|| action.equals(Intent.ACTION_CREATE_DOCUMENT)
|
|
||||||
|| action.equals(Intent.ACTION_OPEN_DOCUMENT)
|
|
||||||
|| action.equals(Intent.ACTION_OPEN_DOCUMENT_TREE));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,6 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.biometric.BiometricPrompt;
|
import androidx.biometric.BiometricPrompt;
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.AegisApplication;
|
|
||||||
import com.beemdevelopment.aegis.Preferences;
|
|
||||||
import com.beemdevelopment.aegis.R;
|
import com.beemdevelopment.aegis.R;
|
||||||
import com.beemdevelopment.aegis.ThemeMap;
|
import com.beemdevelopment.aegis.ThemeMap;
|
||||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
|
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
|
||||||
|
@ -37,7 +35,7 @@ import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
||||||
import com.beemdevelopment.aegis.ui.tasks.PasswordSlotDecryptTask;
|
import com.beemdevelopment.aegis.ui.tasks.PasswordSlotDecryptTask;
|
||||||
import com.beemdevelopment.aegis.vault.VaultFile;
|
import com.beemdevelopment.aegis.vault.VaultFile;
|
||||||
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
|
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
|
||||||
import com.beemdevelopment.aegis.vault.VaultManagerException;
|
import com.beemdevelopment.aegis.vault.VaultRepositoryException;
|
||||||
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;
|
||||||
import com.beemdevelopment.aegis.vault.slots.Slot;
|
import com.beemdevelopment.aegis.vault.slots.Slot;
|
||||||
|
@ -64,12 +62,9 @@ public class AuthActivity extends AegisActivity {
|
||||||
// biometric prompt by setting 'inhibitBioPrompt' to true through the intent
|
// biometric prompt by setting 'inhibitBioPrompt' to true through the intent
|
||||||
private boolean _inhibitBioPrompt;
|
private boolean _inhibitBioPrompt;
|
||||||
|
|
||||||
private Preferences _prefs;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
_prefs = new Preferences(this);
|
|
||||||
setContentView(R.layout.activity_auth);
|
setContentView(R.layout.activity_auth);
|
||||||
_textPassword = findViewById(R.id.text_password);
|
_textPassword = findViewById(R.id.text_password);
|
||||||
LinearLayout boxBiometricInfo = findViewById(R.id.box_biometric_info);
|
LinearLayout boxBiometricInfo = findViewById(R.id.box_biometric_info);
|
||||||
|
@ -94,15 +89,14 @@ public class AuthActivity extends AegisActivity {
|
||||||
_inhibitBioPrompt = savedInstanceState.getBoolean("inhibitBioPrompt", false);
|
_inhibitBioPrompt = savedInstanceState.getBoolean("inhibitBioPrompt", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (_vaultManager.getVaultFileError() != null) {
|
||||||
VaultFile vaultFile = getApp().loadVaultFile();
|
Dialogs.showErrorDialog(this, R.string.vault_load_error, _vaultManager.getVaultFileError(), (dialog, which) -> onBackPressed());
|
||||||
_slots = vaultFile.getHeader().getSlots();
|
|
||||||
} catch (VaultManagerException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
Dialogs.showErrorDialog(this, R.string.vault_load_error, e, (dialog, which) -> onBackPressed());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VaultFile vaultFile = _vaultManager.getVaultFile();
|
||||||
|
_slots = vaultFile.getHeader().getSlots();
|
||||||
|
|
||||||
// only show the biometric prompt if the api version is new enough, permission is granted, a scanner is found and a biometric slot is found
|
// only show the biometric prompt if the api version is new enough, permission is granted, a scanner is found and a biometric slot is found
|
||||||
if (_slots.has(BiometricSlot.class) && BiometricsHelper.isAvailable(this)) {
|
if (_slots.has(BiometricSlot.class) && BiometricsHelper.isAvailable(this)) {
|
||||||
boolean invalidated = false;
|
boolean invalidated = false;
|
||||||
|
@ -266,12 +260,11 @@ public class AuthActivity extends AegisActivity {
|
||||||
VaultFileCredentials creds = new VaultFileCredentials(key, _slots);
|
VaultFileCredentials creds = new VaultFileCredentials(key, _slots);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
AegisApplication app = getApp();
|
_vaultManager.unlock(creds);
|
||||||
app.initVaultManager(app.loadVaultFile(), creds);
|
|
||||||
if (isSlotRepaired) {
|
if (isSlotRepaired) {
|
||||||
saveVault(true);
|
saveAndBackupVault();
|
||||||
}
|
}
|
||||||
} catch (VaultManagerException e) {
|
} catch (VaultRepositoryException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
Dialogs.showErrorDialog(this, R.string.decryption_corrupt_error, e);
|
Dialogs.showErrorDialog(this, R.string.decryption_corrupt_error, e);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -55,7 +55,7 @@ import com.beemdevelopment.aegis.ui.views.IconAdapter;
|
||||||
import com.beemdevelopment.aegis.util.Cloner;
|
import com.beemdevelopment.aegis.util.Cloner;
|
||||||
import com.beemdevelopment.aegis.util.IOUtils;
|
import com.beemdevelopment.aegis.util.IOUtils;
|
||||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||||
import com.beemdevelopment.aegis.vault.VaultManager;
|
import com.beemdevelopment.aegis.vault.VaultRepository;
|
||||||
import com.bumptech.glide.Glide;
|
import com.bumptech.glide.Glide;
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
import com.bumptech.glide.request.target.CustomTarget;
|
import com.bumptech.glide.request.target.CustomTarget;
|
||||||
|
@ -118,16 +118,16 @@ public class EditEntryActivity extends AegisActivity {
|
||||||
private RelativeLayout _advancedSettingsHeader;
|
private RelativeLayout _advancedSettingsHeader;
|
||||||
private RelativeLayout _advancedSettings;
|
private RelativeLayout _advancedSettings;
|
||||||
|
|
||||||
private VaultManager _vault;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
if (abortIfOrphan(savedInstanceState)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setContentView(R.layout.activity_edit_entry);
|
setContentView(R.layout.activity_edit_entry);
|
||||||
setSupportActionBar(findViewById(R.id.toolbar));
|
setSupportActionBar(findViewById(R.id.toolbar));
|
||||||
|
|
||||||
_vault = getApp().getVaultManager();
|
_groups = _vaultManager.getVault().getGroups();
|
||||||
_groups = _vault.getGroups();
|
|
||||||
|
|
||||||
ActionBar bar = getSupportActionBar();
|
ActionBar bar = getSupportActionBar();
|
||||||
bar.setHomeAsUpIndicator(R.drawable.ic_close);
|
bar.setHomeAsUpIndicator(R.drawable.ic_close);
|
||||||
|
@ -137,7 +137,7 @@ public class EditEntryActivity extends AegisActivity {
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
UUID entryUUID = (UUID) intent.getSerializableExtra("entryUUID");
|
UUID entryUUID = (UUID) intent.getSerializableExtra("entryUUID");
|
||||||
if (entryUUID != null) {
|
if (entryUUID != null) {
|
||||||
_origEntry = _vault.getEntryByUUID(entryUUID);
|
_origEntry = _vaultManager.getVault().getEntryByUUID(entryUUID);
|
||||||
} else {
|
} else {
|
||||||
_origEntry = (VaultEntry) intent.getSerializableExtra("newEntry");
|
_origEntry = (VaultEntry) intent.getSerializableExtra("newEntry");
|
||||||
_isManual = intent.getBooleanExtra("isManual", false);
|
_isManual = intent.getBooleanExtra("isManual", false);
|
||||||
|
@ -170,7 +170,7 @@ public class EditEntryActivity extends AegisActivity {
|
||||||
DropdownHelper.fillDropdown(this, _dropdownGroup, _dropdownGroupList);
|
DropdownHelper.fillDropdown(this, _dropdownGroup, _dropdownGroupList);
|
||||||
|
|
||||||
// if this is NOT a manually entered entry, move the "Secret" field from basic to advanced settings
|
// if this is NOT a manually entered entry, move the "Secret" field from basic to advanced settings
|
||||||
if (!_isNew || (_isNew && !_isManual)) {
|
if (!_isNew || !_isManual) {
|
||||||
int secretIndex = 0;
|
int secretIndex = 0;
|
||||||
LinearLayout layoutSecret = findViewById(R.id.layout_secret);
|
LinearLayout layoutSecret = findViewById(R.id.layout_secret);
|
||||||
LinearLayout layoutBasic = findViewById(R.id.layout_basic);
|
LinearLayout layoutBasic = findViewById(R.id.layout_basic);
|
||||||
|
@ -313,7 +313,7 @@ public class EditEntryActivity extends AegisActivity {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
_textUsageCount.setText(getPreferences().getUsageCount(entryUUID).toString());
|
_textUsageCount.setText(_prefs.getUsageCount(entryUUID).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateAdvancedFieldStatus(String otpType) {
|
private void updateAdvancedFieldStatus(String otpType) {
|
||||||
|
@ -476,16 +476,16 @@ public class EditEntryActivity extends AegisActivity {
|
||||||
|
|
||||||
Intent chooserIntent = Intent.createChooser(galleryIntent, getString(R.string.select_icon));
|
Intent chooserIntent = Intent.createChooser(galleryIntent, getString(R.string.select_icon));
|
||||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { fileIntent });
|
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { fileIntent });
|
||||||
AegisActivity.Helper.startExtActivityForResult(this, chooserIntent, PICK_IMAGE_REQUEST);
|
_vaultManager.startActivityForResult(this, chooserIntent, PICK_IMAGE_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetUsageCount() {
|
private void resetUsageCount() {
|
||||||
getPreferences().resetUsageCount(_origEntry.getUUID());
|
_prefs.resetUsageCount(_origEntry.getUUID());
|
||||||
_textUsageCount.setText("0");
|
_textUsageCount.setText("0");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startIconSelection() {
|
private void startIconSelection() {
|
||||||
List<IconPack> iconPacks = getApp().getIconPackManager().getIconPacks().stream()
|
List<IconPack> iconPacks = _iconPackManager.getIconPacks().stream()
|
||||||
.sorted(Comparator.comparing(IconPack::getName))
|
.sorted(Comparator.comparing(IconPack::getName))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
if (iconPacks.size() == 0) {
|
if (iconPacks.size() == 0) {
|
||||||
|
@ -579,17 +579,18 @@ public class EditEntryActivity extends AegisActivity {
|
||||||
// vault to disk failed, causing the user to tap 'Save' again. Calling addEntry
|
// vault to disk failed, causing the user to tap 'Save' again. Calling addEntry
|
||||||
// again would cause a crash in that case, so the isEntryDuplicate check prevents
|
// again would cause a crash in that case, so the isEntryDuplicate check prevents
|
||||||
// that.
|
// that.
|
||||||
if (_isNew && !_vault.isEntryDuplicate(entry)) {
|
VaultRepository vault = _vaultManager.getVault();
|
||||||
_vault.addEntry(entry);
|
if (_isNew && !vault.isEntryDuplicate(entry)) {
|
||||||
|
vault.addEntry(entry);
|
||||||
} else {
|
} else {
|
||||||
_vault.replaceEntry(entry);
|
vault.replaceEntry(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
saveAndFinish(entry, false);
|
saveAndFinish(entry, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteAndFinish(VaultEntry entry) {
|
private void deleteAndFinish(VaultEntry entry) {
|
||||||
_vault.removeEntry(entry);
|
_vaultManager.getVault().removeEntry(entry);
|
||||||
saveAndFinish(entry, true);
|
saveAndFinish(entry, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -598,7 +599,7 @@ public class EditEntryActivity extends AegisActivity {
|
||||||
intent.putExtra("entryUUID", entry.getUUID());
|
intent.putExtra("entryUUID", entry.getUUID());
|
||||||
intent.putExtra("delete", delete);
|
intent.putExtra("delete", delete);
|
||||||
|
|
||||||
if (saveVault(true)) {
|
if (saveAndBackupVault()) {
|
||||||
setResult(RESULT_OK, intent);
|
setResult(RESULT_OK, intent);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,9 @@ public class GroupManagerActivity extends AegisActivity implements GroupAdapter.
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
if (abortIfOrphan(savedInstanceState)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setContentView(R.layout.activity_groups);
|
setContentView(R.layout.activity_groups);
|
||||||
setSupportActionBar(findViewById(R.id.toolbar));
|
setSupportActionBar(findViewById(R.id.toolbar));
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ import com.beemdevelopment.aegis.ui.models.ImportEntry;
|
||||||
import com.beemdevelopment.aegis.ui.views.ImportEntriesAdapter;
|
import com.beemdevelopment.aegis.ui.views.ImportEntriesAdapter;
|
||||||
import com.beemdevelopment.aegis.util.UUIDMap;
|
import com.beemdevelopment.aegis.util.UUIDMap;
|
||||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||||
import com.beemdevelopment.aegis.vault.VaultManager;
|
import com.beemdevelopment.aegis.vault.VaultRepository;
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
import com.topjohnwu.superuser.Shell;
|
import com.topjohnwu.superuser.Shell;
|
||||||
|
|
||||||
|
@ -47,6 +47,9 @@ public class ImportEntriesActivity extends AegisActivity {
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
if (abortIfOrphan(savedInstanceState)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setContentView(R.layout.activity_import_entries);
|
setContentView(R.layout.activity_import_entries);
|
||||||
setSupportActionBar(findViewById(R.id.toolbar));
|
setSupportActionBar(findViewById(R.id.toolbar));
|
||||||
|
|
||||||
|
@ -71,7 +74,7 @@ public class ImportEntriesActivity extends AegisActivity {
|
||||||
|
|
||||||
FloatingActionButton fab = findViewById(R.id.fab);
|
FloatingActionButton fab = findViewById(R.id.fab);
|
||||||
fab.setOnClickListener(v -> {
|
fab.setOnClickListener(v -> {
|
||||||
if (getApp().getVaultManager().getEntries().size() > 0
|
if (_vaultManager.getVault().getEntries().size() > 0
|
||||||
&& _menu.findItem(R.id.toggle_wipe_vault).isChecked()) {
|
&& _menu.findItem(R.id.toggle_wipe_vault).isChecked()) {
|
||||||
showWipeEntriesDialog();
|
showWipeEntriesDialog();
|
||||||
} else {
|
} else {
|
||||||
|
@ -200,7 +203,7 @@ public class ImportEntriesActivity extends AegisActivity {
|
||||||
.setMessage(message)
|
.setMessage(message)
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
.setNeutralButton(android.R.string.copy, (dialog2, which2) -> {
|
.setNeutralButton(android.R.string.copy, (dialog2, which2) -> {
|
||||||
ClipboardManager clipboard = (ClipboardManager) this.getSystemService(Context.CLIPBOARD_SERVICE);
|
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
ClipData clip = ClipData.newPlainText("text/plain", message);
|
ClipData clip = ClipData.newPlainText("text/plain", message);
|
||||||
clipboard.setPrimaryClip(clip);
|
clipboard.setPrimaryClip(clip);
|
||||||
Toast.makeText(this, R.string.errors_copied, Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, R.string.errors_copied, Toast.LENGTH_SHORT).show();
|
||||||
|
@ -217,7 +220,7 @@ public class ImportEntriesActivity extends AegisActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveAndFinish(boolean wipeEntries) {
|
private void saveAndFinish(boolean wipeEntries) {
|
||||||
VaultManager vault = getApp().getVaultManager();
|
VaultRepository vault = _vaultManager.getVault();
|
||||||
if (wipeEntries) {
|
if (wipeEntries) {
|
||||||
vault.wipeEntries();
|
vault.wipeEntries();
|
||||||
}
|
}
|
||||||
|
@ -234,7 +237,7 @@ public class ImportEntriesActivity extends AegisActivity {
|
||||||
vault.addEntry(entry);
|
vault.addEntry(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (saveVault(true)) {
|
if (saveAndBackupVault()) {
|
||||||
String toastMessage = getResources().getQuantityString(R.plurals.imported_entries_count, selectedEntries.size(), selectedEntries.size());
|
String toastMessage = getResources().getQuantityString(R.plurals.imported_entries_count, selectedEntries.size(), selectedEntries.size());
|
||||||
Toast.makeText(this, toastMessage, Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, toastMessage, Toast.LENGTH_SHORT).show();
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
package com.beemdevelopment.aegis.ui;
|
package com.beemdevelopment.aegis.ui;
|
||||||
|
|
||||||
|
import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_BIOMETRIC;
|
||||||
|
import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_INVALID;
|
||||||
|
import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_NONE;
|
||||||
|
import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_PASS;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
|
||||||
|
@ -12,22 +17,11 @@ import com.beemdevelopment.aegis.ui.slides.DoneSlide;
|
||||||
import com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide;
|
import com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide;
|
||||||
import com.beemdevelopment.aegis.ui.slides.SecuritySetupSlide;
|
import com.beemdevelopment.aegis.ui.slides.SecuritySetupSlide;
|
||||||
import com.beemdevelopment.aegis.ui.slides.WelcomeSlide;
|
import com.beemdevelopment.aegis.ui.slides.WelcomeSlide;
|
||||||
import com.beemdevelopment.aegis.vault.Vault;
|
|
||||||
import com.beemdevelopment.aegis.vault.VaultFile;
|
|
||||||
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
|
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
|
||||||
import com.beemdevelopment.aegis.vault.VaultFileException;
|
import com.beemdevelopment.aegis.vault.VaultRepositoryException;
|
||||||
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.BiometricSlot;
|
||||||
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
||||||
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_BIOMETRIC;
|
|
||||||
import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_INVALID;
|
|
||||||
import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_NONE;
|
|
||||||
import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_PASS;
|
|
||||||
|
|
||||||
public class IntroActivity extends IntroBaseActivity {
|
public class IntroActivity extends IntroBaseActivity {
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
@ -73,31 +67,16 @@ public class IntroActivity extends IntroBaseActivity {
|
||||||
throw new RuntimeException(String.format("State of SecuritySetupSlide not properly propagated, cryptType: %d, creds: %s", cryptType, creds));
|
throw new RuntimeException(String.format("State of SecuritySetupSlide not properly propagated, cryptType: %d, creds: %s", cryptType, creds));
|
||||||
}
|
}
|
||||||
|
|
||||||
Vault vault = new Vault();
|
|
||||||
VaultFile vaultFile = new VaultFile();
|
|
||||||
try {
|
try {
|
||||||
JSONObject obj = vault.toJson();
|
_vaultManager.init(creds);
|
||||||
if (cryptType == CRYPT_TYPE_NONE) {
|
} catch (VaultRepositoryException e) {
|
||||||
vaultFile.setContent(obj);
|
|
||||||
} else {
|
|
||||||
vaultFile.setContent(obj, creds);
|
|
||||||
}
|
|
||||||
|
|
||||||
VaultManager.save(getApplicationContext(), vaultFile);
|
|
||||||
} catch (VaultManagerException | VaultFileException 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cryptType == CRYPT_TYPE_NONE) {
|
|
||||||
getApp().initVaultManager(vault, null);
|
|
||||||
} else {
|
|
||||||
getApp().initVaultManager(vault, creds);
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip the intro from now on
|
// skip the intro from now on
|
||||||
getPreferences().setIntroDone(true);
|
_prefs.setIntroDone(true);
|
||||||
|
|
||||||
setResult(RESULT_OK);
|
setResult(RESULT_OK);
|
||||||
finish();
|
finish();
|
||||||
|
|
|
@ -17,11 +17,9 @@ import android.view.View;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.appcompat.view.ActionMode;
|
import androidx.appcompat.view.ActionMode;
|
||||||
import androidx.appcompat.widget.SearchView;
|
import androidx.appcompat.widget.SearchView;
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.AegisApplication;
|
|
||||||
import com.beemdevelopment.aegis.Preferences;
|
import com.beemdevelopment.aegis.Preferences;
|
||||||
import com.beemdevelopment.aegis.R;
|
import com.beemdevelopment.aegis.R;
|
||||||
import com.beemdevelopment.aegis.SortCategory;
|
import com.beemdevelopment.aegis.SortCategory;
|
||||||
|
@ -33,13 +31,10 @@ import com.beemdevelopment.aegis.helpers.QrCodeAnalyzer;
|
||||||
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
|
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
|
||||||
import com.beemdevelopment.aegis.otp.GoogleAuthInfoException;
|
import com.beemdevelopment.aegis.otp.GoogleAuthInfoException;
|
||||||
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
||||||
import com.beemdevelopment.aegis.ui.fragments.BackupsPreferencesFragment;
|
import com.beemdevelopment.aegis.ui.fragments.preferences.BackupsPreferencesFragment;
|
||||||
import com.beemdevelopment.aegis.ui.fragments.PreferencesFragment;
|
import com.beemdevelopment.aegis.ui.fragments.preferences.PreferencesFragment;
|
||||||
import com.beemdevelopment.aegis.ui.views.EntryListView;
|
import com.beemdevelopment.aegis.ui.views.EntryListView;
|
||||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||||
import com.beemdevelopment.aegis.vault.VaultFile;
|
|
||||||
import com.beemdevelopment.aegis.vault.VaultManager;
|
|
||||||
import com.beemdevelopment.aegis.vault.VaultManagerException;
|
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
import com.google.zxing.BinaryBitmap;
|
import com.google.zxing.BinaryBitmap;
|
||||||
|
@ -58,6 +53,7 @@ import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.TreeSet;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class MainActivity extends AegisActivity implements EntryListView.Listener {
|
public class MainActivity extends AegisActivity implements EntryListView.Listener {
|
||||||
|
@ -74,13 +70,8 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
private static final int CODE_PERM_CAMERA = 0;
|
private static final int CODE_PERM_CAMERA = 0;
|
||||||
private static final int CODE_PERM_READ_STORAGE = 1;
|
private static final int CODE_PERM_READ_STORAGE = 1;
|
||||||
|
|
||||||
private AegisApplication _app;
|
|
||||||
private VaultManager _vault;
|
|
||||||
private boolean _loaded;
|
private boolean _loaded;
|
||||||
private boolean _searchSubmitted;
|
private boolean _searchSubmitted;
|
||||||
|
|
||||||
private boolean _isAuthenticating;
|
|
||||||
private boolean _isDoingIntro;
|
|
||||||
private boolean _isRecreated;
|
private boolean _isRecreated;
|
||||||
|
|
||||||
private List<VaultEntry> _selectedEntries;
|
private List<VaultEntry> _selectedEntries;
|
||||||
|
@ -100,29 +91,24 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
setSupportActionBar(findViewById(R.id.toolbar));
|
setSupportActionBar(findViewById(R.id.toolbar));
|
||||||
|
|
||||||
_app = (AegisApplication) getApplication();
|
|
||||||
_vault = _app.getVaultManager();
|
|
||||||
_loaded = false;
|
_loaded = false;
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
_isRecreated = true;
|
_isRecreated = true;
|
||||||
_isAuthenticating = savedInstanceState.getBoolean("isAuthenticating");
|
|
||||||
_isDoingIntro = savedInstanceState.getBoolean("isDoingIntro");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_entryListView = (EntryListView) getSupportFragmentManager().findFragmentById(R.id.key_profiles);
|
_entryListView = (EntryListView) getSupportFragmentManager().findFragmentById(R.id.key_profiles);
|
||||||
_entryListView.setListener(this);
|
_entryListView.setListener(this);
|
||||||
_entryListView.setCodeGroupSize(getPreferences().getCodeGroupSize());
|
_entryListView.setCodeGroupSize(_prefs.getCodeGroupSize());
|
||||||
_entryListView.setShowAccountName(getPreferences().isAccountNameVisible());
|
_entryListView.setShowAccountName(_prefs.isAccountNameVisible());
|
||||||
_entryListView.setHighlightEntry(getPreferences().isEntryHighlightEnabled());
|
_entryListView.setHighlightEntry(_prefs.isEntryHighlightEnabled());
|
||||||
_entryListView.setPauseFocused(getPreferences().isPauseFocusedEnabled());
|
_entryListView.setPauseFocused(_prefs.isPauseFocusedEnabled());
|
||||||
_entryListView.setTapToReveal(getPreferences().isTapToRevealEnabled());
|
_entryListView.setTapToReveal(_prefs.isTapToRevealEnabled());
|
||||||
_entryListView.setTapToRevealTime(getPreferences().getTapToRevealTime());
|
_entryListView.setTapToRevealTime(_prefs.getTapToRevealTime());
|
||||||
_entryListView.setSortCategory(getPreferences().getCurrentSortCategory(), false);
|
_entryListView.setSortCategory(_prefs.getCurrentSortCategory(), false);
|
||||||
_entryListView.setViewMode(getPreferences().getCurrentViewMode());
|
_entryListView.setViewMode(_prefs.getCurrentViewMode());
|
||||||
_entryListView.setIsCopyOnTapEnabled(getPreferences().isCopyOnTapEnabled());
|
_entryListView.setIsCopyOnTapEnabled(_prefs.isCopyOnTapEnabled());
|
||||||
_entryListView.setPrefGroupFilter(getPreferences().getGroupFilter());
|
_entryListView.setPrefGroupFilter(_prefs.getGroupFilter());
|
||||||
|
|
||||||
FloatingActionButton fab = findViewById(R.id.fab);
|
FloatingActionButton fab = findViewById(R.id.fab);
|
||||||
fab.setOnClickListener(v -> {
|
fab.setOnClickListener(v -> {
|
||||||
|
@ -155,13 +141,6 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
_selectedEntries = new ArrayList<>();
|
_selectedEntries = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
outState.putBoolean("isAuthenticating", _isAuthenticating);
|
|
||||||
outState.putBoolean("isDoingIntro", _isDoingIntro);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
_entryListView.setListener(null);
|
_entryListView.setListener(null);
|
||||||
|
@ -172,18 +151,14 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
protected void onPause() {
|
protected void onPause() {
|
||||||
Map<UUID, Integer> usageMap = _entryListView.getUsageCounts();
|
Map<UUID, Integer> usageMap = _entryListView.getUsageCounts();
|
||||||
if (usageMap != null) {
|
if (usageMap != null) {
|
||||||
getPreferences().setUsageCount(usageMap);
|
_prefs.setUsageCount(usageMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
super.onPause();
|
super.onPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
_isAuthenticating = false;
|
|
||||||
_isDoingIntro = false;
|
|
||||||
|
|
||||||
if (resultCode != RESULT_OK) {
|
if (resultCode != RESULT_OK) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -199,7 +174,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
onEditEntryResult(data);
|
onEditEntryResult(data);
|
||||||
break;
|
break;
|
||||||
case CODE_DO_INTRO:
|
case CODE_DO_INTRO:
|
||||||
onDoIntroResult();
|
onIntroResult();
|
||||||
break;
|
break;
|
||||||
case CODE_DECRYPT:
|
case CODE_DECRYPT:
|
||||||
onDecryptResult();
|
onDecryptResult();
|
||||||
|
@ -239,14 +214,14 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
if (data.getBooleanExtra("needsRecreate", false)) {
|
if (data.getBooleanExtra("needsRecreate", false)) {
|
||||||
recreate();
|
recreate();
|
||||||
} else if (data.getBooleanExtra("needsRefresh", false)) {
|
} else if (data.getBooleanExtra("needsRefresh", false)) {
|
||||||
boolean showAccountName = getPreferences().isAccountNameVisible();
|
boolean showAccountName = _prefs.isAccountNameVisible();
|
||||||
int codeGroupSize = getPreferences().getCodeGroupSize();
|
int codeGroupSize = _prefs.getCodeGroupSize();
|
||||||
boolean highlightEntry = getPreferences().isEntryHighlightEnabled();
|
boolean highlightEntry = _prefs.isEntryHighlightEnabled();
|
||||||
boolean pauseFocused = getPreferences().isPauseFocusedEnabled();
|
boolean pauseFocused = _prefs.isPauseFocusedEnabled();
|
||||||
boolean tapToReveal = getPreferences().isTapToRevealEnabled();
|
boolean tapToReveal = _prefs.isTapToRevealEnabled();
|
||||||
int tapToRevealTime = getPreferences().getTapToRevealTime();
|
int tapToRevealTime = _prefs.getTapToRevealTime();
|
||||||
ViewMode viewMode = getPreferences().getCurrentViewMode();
|
ViewMode viewMode = _prefs.getCurrentViewMode();
|
||||||
boolean copyOnTap = getPreferences().isCopyOnTapEnabled();
|
boolean copyOnTap = _prefs.isCopyOnTapEnabled();
|
||||||
_entryListView.setShowAccountName(showAccountName);
|
_entryListView.setShowAccountName(showAccountName);
|
||||||
_entryListView.setCodeGroupSize(codeGroupSize);
|
_entryListView.setCodeGroupSize(codeGroupSize);
|
||||||
_entryListView.setHighlightEntry(highlightEntry);
|
_entryListView.setHighlightEntry(highlightEntry);
|
||||||
|
@ -286,20 +261,20 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
startEditEntryActivityForNew(CODE_ADD_ENTRY, entries.get(0));
|
startEditEntryActivityForNew(CODE_ADD_ENTRY, entries.get(0));
|
||||||
} else {
|
} else {
|
||||||
for (VaultEntry entry : entries) {
|
for (VaultEntry entry : entries) {
|
||||||
_vault.addEntry(entry);
|
_vaultManager.getVault().addEntry(entry);
|
||||||
if (_loaded) {
|
if (_loaded) {
|
||||||
_entryListView.addEntry(entry);
|
_entryListView.addEntry(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
saveVault(true);
|
saveAndBackupVault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onAddEntryResult(Intent data) {
|
private void onAddEntryResult(Intent data) {
|
||||||
if (_loaded) {
|
if (_loaded) {
|
||||||
UUID entryUUID = (UUID) data.getSerializableExtra("entryUUID");
|
UUID entryUUID = (UUID) data.getSerializableExtra("entryUUID");
|
||||||
VaultEntry entry = _vault.getEntryByUUID(entryUUID);
|
VaultEntry entry = _vaultManager.getVault().getEntryByUUID(entryUUID);
|
||||||
_entryListView.addEntry(entry, true);
|
_entryListView.addEntry(entry, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -311,7 +286,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
if (data.getBooleanExtra("delete", false)) {
|
if (data.getBooleanExtra("delete", false)) {
|
||||||
_entryListView.removeEntry(entryUUID);
|
_entryListView.removeEntry(entryUUID);
|
||||||
} else {
|
} else {
|
||||||
VaultEntry entry = _vault.getEntryByUUID(entryUUID);
|
VaultEntry entry = _vaultManager.getVault().getEntryByUUID(entryUUID);
|
||||||
_entryListView.replaceEntry(entryUUID, entry);
|
_entryListView.replaceEntry(entryUUID, entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -352,19 +327,18 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSortCategoryMenu() {
|
private void updateSortCategoryMenu() {
|
||||||
SortCategory category = getPreferences().getCurrentSortCategory();
|
SortCategory category = _prefs.getCurrentSortCategory();
|
||||||
_menu.findItem(category.getMenuItem()).setChecked(true);
|
_menu.findItem(category.getMenuItem()).setChecked(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDoIntroResult() {
|
private void onIntroResult() {
|
||||||
_vault = _app.getVaultManager();
|
|
||||||
loadEntries();
|
loadEntries();
|
||||||
checkTimeSyncSetting();
|
checkTimeSyncSetting();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkTimeSyncSetting() {
|
private void checkTimeSyncSetting() {
|
||||||
boolean autoTime = Settings.Global.getInt(getContentResolver(), Settings.Global.AUTO_TIME, 1) == 1;
|
boolean autoTime = Settings.Global.getInt(getContentResolver(), Settings.Global.AUTO_TIME, 1) == 1;
|
||||||
if (!autoTime && getPreferences().isTimeSyncWarningEnabled()) {
|
if (!autoTime && _prefs.isTimeSyncWarningEnabled()) {
|
||||||
Dialogs.showTimeSyncWarningDialog(this, (dialog, which) -> {
|
Dialogs.showTimeSyncWarningDialog(this, (dialog, which) -> {
|
||||||
Intent intent = new Intent(Settings.ACTION_DATE_SETTINGS);
|
Intent intent = new Intent(Settings.ACTION_DATE_SETTINGS);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
|
@ -373,7 +347,6 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDecryptResult() {
|
private void onDecryptResult() {
|
||||||
_vault = _app.getVaultManager();
|
|
||||||
loadEntries();
|
loadEntries();
|
||||||
checkTimeSyncSetting();
|
checkTimeSyncSetting();
|
||||||
}
|
}
|
||||||
|
@ -396,7 +369,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
|
|
||||||
Intent chooserIntent = Intent.createChooser(galleryIntent, getString(R.string.select_picture));
|
Intent chooserIntent = Intent.createChooser(galleryIntent, getString(R.string.select_picture));
|
||||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { fileIntent });
|
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { fileIntent });
|
||||||
AegisActivity.Helper.startExtActivityForResult(this, chooserIntent, CODE_SCAN_IMAGE);
|
_vaultManager.startActivityForResult(this, chooserIntent, CODE_SCAN_IMAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startPreferencesActivity() {
|
private void startPreferencesActivity() {
|
||||||
|
@ -413,7 +386,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
private void doShortcutActions() {
|
private void doShortcutActions() {
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
String action = intent.getStringExtra("action");
|
String action = intent.getStringExtra("action");
|
||||||
if (action == null || _app.isVaultLocked()) {
|
if (action == null || !_vaultManager.isVaultLoaded()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -427,7 +400,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleDeeplink() {
|
private void handleDeeplink() {
|
||||||
if (_app.isVaultLocked()) {
|
if (!_vaultManager.isVaultLoaded()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -453,7 +426,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleSharedImage() {
|
private void handleSharedImage() {
|
||||||
if (_app.isVaultLocked()) {
|
if (!_vaultManager.isVaultLoaded()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -473,40 +446,28 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
if (_vault == null) {
|
if (_vaultManager.isVaultInitNeeded()) {
|
||||||
// start the intro if the vault file doesn't exist
|
if (_prefs.isIntroDone()) {
|
||||||
if (!_isDoingIntro && !VaultManager.fileExists(this)) {
|
|
||||||
if (getPreferences().isIntroDone()) {
|
|
||||||
Toast.makeText(this, getString(R.string.vault_not_found), Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, getString(R.string.vault_not_found), Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
Intent intro = new Intent(this, IntroActivity.class);
|
Intent intro = new Intent(this, IntroActivity.class);
|
||||||
startActivityForResult(intro, CODE_DO_INTRO);
|
startActivityForResult(intro, CODE_DO_INTRO);
|
||||||
_isDoingIntro = true;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// read the vault from disk
|
if (!_vaultManager.isVaultLoaded() && !_vaultManager.isVaultFileLoaded()) {
|
||||||
// if this fails, show the error to the user and close the app
|
Dialogs.showErrorDialog(this, R.string.vault_load_error, _vaultManager.getVaultFileError(), (dialog1, which) -> finish());
|
||||||
try {
|
|
||||||
VaultFile vaultFile = _app.loadVaultFile();
|
|
||||||
if (!vaultFile.isEncrypted()) {
|
|
||||||
_vault = _app.initVaultManager(vaultFile, null);
|
|
||||||
}
|
|
||||||
} catch (VaultManagerException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
Dialogs.showErrorDialog(this, R.string.vault_load_error, e, (dialog1, which) -> finish());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (_app.isVaultLocked()) {
|
if (!_vaultManager.isVaultLoaded()) {
|
||||||
startAuthActivity(false);
|
startAuthActivity(false);
|
||||||
} else if (_loaded) {
|
} else if (_loaded) {
|
||||||
// update the list of groups in the entry list view so that the chip gets updated
|
// update the list of groups in the entry list view so that the chip gets updated
|
||||||
_entryListView.setGroups(_vault.getGroups());
|
_entryListView.setGroups(_vaultManager.getVault().getGroups());
|
||||||
|
|
||||||
// update the usage counts in case they are edited outside of the entrylistview
|
// update the usage counts in case they are edited outside of the EntryListView
|
||||||
_entryListView.setUsageCounts(getPreferences().getUsageCounts());
|
_entryListView.setUsageCounts(_prefs.getUsageCounts());
|
||||||
|
|
||||||
// refresh all codes to prevent showing old ones
|
// refresh all codes to prevent showing old ones
|
||||||
_entryListView.refresh(false);
|
_entryListView.refresh(false);
|
||||||
|
@ -533,8 +494,8 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_app.isAutoLockEnabled(Preferences.AUTO_LOCK_ON_BACK_BUTTON)) {
|
if (_vaultManager.isAutoLockEnabled(Preferences.AUTO_LOCK_ON_BACK_BUTTON)) {
|
||||||
_app.lock(false);
|
_vaultManager.lock(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -543,11 +504,11 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
|
|
||||||
private void deleteEntries(List<VaultEntry> entries) {
|
private void deleteEntries(List<VaultEntry> entries) {
|
||||||
for (VaultEntry entry: entries) {
|
for (VaultEntry entry: entries) {
|
||||||
VaultEntry oldEntry = _vault.removeEntry(entry);
|
VaultEntry oldEntry = _vaultManager.getVault().removeEntry(entry);
|
||||||
_entryListView.removeEntry(oldEntry);
|
_entryListView.removeEntry(oldEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
saveVault(true);
|
saveAndBackupVault();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -556,7 +517,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
getMenuInflater().inflate(R.menu.menu_main, menu);
|
getMenuInflater().inflate(R.menu.menu_main, menu);
|
||||||
updateLockIcon();
|
updateLockIcon();
|
||||||
if (_loaded) {
|
if (_loaded) {
|
||||||
_entryListView.setGroups(_vault.getGroups());
|
_entryListView.setGroups(_vaultManager.getVault().getGroups());
|
||||||
updateSortCategoryMenu();
|
updateSortCategoryMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -565,7 +526,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
_searchView = (SearchView) searchViewMenuItem.getActionView();
|
_searchView = (SearchView) searchViewMenuItem.getActionView();
|
||||||
|
|
||||||
_searchView.setQueryHint(getString(R.string.search));
|
_searchView.setQueryHint(getString(R.string.search));
|
||||||
if (getPreferences().getFocusSearchEnabled() && !_isRecreated) {
|
if (_prefs.getFocusSearchEnabled() && !_isRecreated) {
|
||||||
_searchView.setIconified(false);
|
_searchView.setIconified(false);
|
||||||
_searchView.setFocusable(true);
|
_searchView.setFocusable(true);
|
||||||
_searchView.requestFocus();
|
_searchView.requestFocus();
|
||||||
|
@ -612,7 +573,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case R.id.action_lock:
|
case R.id.action_lock:
|
||||||
_app.lock(true);
|
_vaultManager.lock(true);
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
if (item.getGroupId() == R.id.action_sort_category) {
|
if (item.getGroupId() == R.id.action_sort_category) {
|
||||||
|
@ -642,7 +603,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
}
|
}
|
||||||
|
|
||||||
_entryListView.setSortCategory(sortCategory, true);
|
_entryListView.setSortCategory(sortCategory, true);
|
||||||
getPreferences().setCurrentSortCategory(sortCategory);
|
_prefs.setCurrentSortCategory(sortCategory);
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
@ -655,34 +616,31 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
|
|
||||||
private void loadEntries() {
|
private void loadEntries() {
|
||||||
if (!_loaded) {
|
if (!_loaded) {
|
||||||
_entryListView.setUsageCounts(getPreferences().getUsageCounts());
|
_entryListView.setUsageCounts(_prefs.getUsageCounts());
|
||||||
_entryListView.addEntries(_vault.getEntries());
|
_entryListView.addEntries(_vaultManager.getVault().getEntries());
|
||||||
_entryListView.runEntriesAnimation();
|
_entryListView.runEntriesAnimation();
|
||||||
_loaded = true;
|
_loaded = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startAuthActivity(boolean inhibitBioPrompt) {
|
private void startAuthActivity(boolean inhibitBioPrompt) {
|
||||||
if (!_isAuthenticating) {
|
|
||||||
Intent intent = new Intent(this, AuthActivity.class);
|
Intent intent = new Intent(this, AuthActivity.class);
|
||||||
intent.putExtra("inhibitBioPrompt", inhibitBioPrompt);
|
intent.putExtra("inhibitBioPrompt", inhibitBioPrompt);
|
||||||
startActivityForResult(intent, CODE_DECRYPT);
|
startActivityForResult(intent, CODE_DECRYPT);
|
||||||
_isAuthenticating = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateLockIcon() {
|
private void updateLockIcon() {
|
||||||
// hide the lock icon if the vault is not unlocked
|
// hide the lock icon if the vault is not unlocked
|
||||||
if (_menu != null && !_app.isVaultLocked()) {
|
if (_menu != null && _vaultManager.isVaultLoaded()) {
|
||||||
MenuItem item = _menu.findItem(R.id.action_lock);
|
MenuItem item = _menu.findItem(R.id.action_lock);
|
||||||
item.setVisible(_vault.isEncryptionEnabled());
|
item.setVisible(_vaultManager.getVault().isEncryptionEnabled());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateBackupErrorBar() {
|
private void updateBackupErrorBar() {
|
||||||
String error = null;
|
String error = null;
|
||||||
if (_app.getPreferences().isBackupsEnabled()) {
|
if (_prefs.isBackupsEnabled()) {
|
||||||
error = _app.getPreferences().getBackupsError();
|
error = _prefs.getBackupsError();
|
||||||
}
|
}
|
||||||
|
|
||||||
_btnBackupError.setVisibility(error == null ? View.GONE : View.VISIBLE);
|
_btnBackupError.setVisibility(error == null ? View.GONE : View.VISIBLE);
|
||||||
|
@ -725,22 +683,22 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
|
|
||||||
_selectedEntries.add(entry);
|
_selectedEntries.add(entry);
|
||||||
_entryListView.setActionModeState(true, entry);
|
_entryListView.setActionModeState(true, entry);
|
||||||
_actionMode = this.startSupportActionMode(_actionModeCallbacks);
|
_actionMode = startSupportActionMode(_actionModeCallbacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEntryMove(VaultEntry entry1, VaultEntry entry2) {
|
public void onEntryMove(VaultEntry entry1, VaultEntry entry2) {
|
||||||
_vault.swapEntries(entry1, entry2);
|
_vaultManager.getVault().swapEntries(entry1, entry2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEntryDrop(VaultEntry entry) {
|
public void onEntryDrop(VaultEntry entry) {
|
||||||
saveVault(false);
|
saveVault();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEntryChange(VaultEntry entry) {
|
public void onEntryChange(VaultEntry entry) {
|
||||||
saveVault(true);
|
saveAndBackupVault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onEntryCopy(VaultEntry entry) {
|
public void onEntryCopy(VaultEntry entry) {
|
||||||
|
@ -757,7 +715,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSaveGroupFilter(List<String> groupFilter) {
|
public void onSaveGroupFilter(List<String> groupFilter) {
|
||||||
getPreferences().setGroupFilter(groupFilter);
|
_prefs.setGroupFilter(groupFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -772,7 +730,6 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
_entryListView.clearEntries();
|
_entryListView.clearEntries();
|
||||||
_loaded = false;
|
_loaded = false;
|
||||||
|
|
||||||
|
|
||||||
if (userInitiated) {
|
if (userInitiated) {
|
||||||
startAuthActivity(true);
|
startAuthActivity(true);
|
||||||
} else {
|
} else {
|
||||||
|
@ -832,8 +789,9 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
|
|
||||||
for (VaultEntry entry : _selectedEntries) {
|
for (VaultEntry entry : _selectedEntries) {
|
||||||
if (entry.getGroup() != null) {
|
if (entry.getGroup() != null) {
|
||||||
if (!_vault.getGroups().contains(entry.getGroup())) {
|
TreeSet<String> groups = _vaultManager.getVault().getGroups();
|
||||||
_entryListView.setGroups(_vault.getGroups());
|
if (!groups.contains(entry.getGroup())) {
|
||||||
|
_entryListView.setGroups(groups);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,9 @@ import android.os.Bundle;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.BuildConfig;
|
import com.beemdevelopment.aegis.BuildConfig;
|
||||||
import com.beemdevelopment.aegis.Preferences;
|
|
||||||
import com.beemdevelopment.aegis.R;
|
import com.beemdevelopment.aegis.R;
|
||||||
import com.beemdevelopment.aegis.crypto.pins.GuardianProjectFDroidRSA2048;
|
import com.beemdevelopment.aegis.crypto.pins.GuardianProjectFDroidRSA2048;
|
||||||
import com.beemdevelopment.aegis.vault.VaultManager;
|
import com.beemdevelopment.aegis.vault.VaultRepository;
|
||||||
|
|
||||||
import info.guardianproject.GuardianProjectRSA4096;
|
import info.guardianproject.GuardianProjectRSA4096;
|
||||||
import info.guardianproject.trustedintents.TrustedIntents;
|
import info.guardianproject.trustedintents.TrustedIntents;
|
||||||
|
@ -19,9 +18,8 @@ public class PanicResponderActivity extends AegisActivity {
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
Preferences prefs = getPreferences();
|
|
||||||
|
|
||||||
if (!prefs.isPanicTriggerEnabled()) {
|
if (!_prefs.isPanicTriggerEnabled()) {
|
||||||
Toast.makeText(this, R.string.panic_trigger_ignore_toast, Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, R.string.panic_trigger_ignore_toast, Toast.LENGTH_SHORT).show();
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
|
@ -39,8 +37,8 @@ public class PanicResponderActivity extends AegisActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (intent != null && PANIC_TRIGGER_ACTION.equals(intent.getAction())) {
|
if (intent != null && PANIC_TRIGGER_ACTION.equals(intent.getAction())) {
|
||||||
getApp().lock(false);
|
VaultRepository.deleteFile(this);
|
||||||
VaultManager.deleteFile(this);
|
_vaultManager.lock(false);
|
||||||
finishApp();
|
finishApp();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,8 @@ import androidx.preference.Preference;
|
||||||
import androidx.preference.PreferenceFragmentCompat;
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.R;
|
import com.beemdevelopment.aegis.R;
|
||||||
import com.beemdevelopment.aegis.ui.fragments.MainPreferencesFragment;
|
import com.beemdevelopment.aegis.ui.fragments.preferences.MainPreferencesFragment;
|
||||||
import com.beemdevelopment.aegis.ui.fragments.PreferencesFragment;
|
import com.beemdevelopment.aegis.ui.fragments.preferences.PreferencesFragment;
|
||||||
|
|
||||||
public class PreferencesActivity extends AegisActivity implements
|
public class PreferencesActivity extends AegisActivity implements
|
||||||
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
|
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
|
||||||
|
@ -18,6 +18,9 @@ public class PreferencesActivity extends AegisActivity implements
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
if (abortIfOrphan(savedInstanceState)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setContentView(R.layout.activity_preferences);
|
setContentView(R.layout.activity_preferences);
|
||||||
setSupportActionBar(findViewById(R.id.toolbar));
|
setSupportActionBar(findViewById(R.id.toolbar));
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,9 @@ public class ScannerActivity extends AegisActivity implements QrCodeAnalyzer.Lis
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
if (abortIfOrphan(savedInstanceState)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setContentView(R.layout.activity_scanner);
|
setContentView(R.layout.activity_scanner);
|
||||||
setSupportActionBar(findViewById(R.id.toolbar));
|
setSupportActionBar(findViewById(R.id.toolbar));
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,9 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
if (abortIfOrphan(savedInstanceState)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setContentView(R.layout.activity_slots);
|
setContentView(R.layout.activity_slots);
|
||||||
setSupportActionBar(findViewById(R.id.toolbar));
|
setSupportActionBar(findViewById(R.id.toolbar));
|
||||||
_edited = false;
|
_edited = false;
|
||||||
|
|
|
@ -17,7 +17,6 @@ import com.beemdevelopment.aegis.R;
|
||||||
import com.beemdevelopment.aegis.Theme;
|
import com.beemdevelopment.aegis.Theme;
|
||||||
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
|
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
|
||||||
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
||||||
import com.beemdevelopment.aegis.vault.VaultManager;
|
|
||||||
import com.google.zxing.BarcodeFormat;
|
import com.google.zxing.BarcodeFormat;
|
||||||
import com.google.zxing.WriterException;
|
import com.google.zxing.WriterException;
|
||||||
import com.google.zxing.common.BitMatrix;
|
import com.google.zxing.common.BitMatrix;
|
||||||
|
@ -34,16 +33,16 @@ public class TransferEntriesActivity extends AegisActivity {
|
||||||
private TextView _entriesCount;
|
private TextView _entriesCount;
|
||||||
private Button _nextButton;
|
private Button _nextButton;
|
||||||
private Button _previousButton;
|
private Button _previousButton;
|
||||||
|
|
||||||
private VaultManager _vault;
|
|
||||||
private int _currentEntryCount = 1;
|
private int _currentEntryCount = 1;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
if (abortIfOrphan(savedInstanceState)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setContentView(R.layout.activity_share_entry);
|
setContentView(R.layout.activity_share_entry);
|
||||||
setSupportActionBar(findViewById(R.id.toolbar));
|
setSupportActionBar(findViewById(R.id.toolbar));
|
||||||
_vault = getApp().getVaultManager();
|
|
||||||
|
|
||||||
_qrImage = findViewById(R.id.ivQrCode);
|
_qrImage = findViewById(R.id.ivQrCode);
|
||||||
_issuer = findViewById(R.id.tvIssuer);
|
_issuer = findViewById(R.id.tvIssuer);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package com.beemdevelopment.aegis.ui.fragments;
|
package com.beemdevelopment.aegis.ui.fragments.preferences;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
@ -8,12 +8,11 @@ import android.os.Bundle;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.Preferences;
|
|
||||||
import com.beemdevelopment.aegis.R;
|
import com.beemdevelopment.aegis.R;
|
||||||
import com.beemdevelopment.aegis.Theme;
|
import com.beemdevelopment.aegis.Theme;
|
||||||
import com.beemdevelopment.aegis.ViewMode;
|
import com.beemdevelopment.aegis.ViewMode;
|
||||||
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
|
||||||
import com.beemdevelopment.aegis.ui.GroupManagerActivity;
|
import com.beemdevelopment.aegis.ui.GroupManagerActivity;
|
||||||
|
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
||||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -27,12 +26,11 @@ public class AppearancePreferencesFragment extends PreferencesFragment {
|
||||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||||
super.onCreatePreferences(savedInstanceState, rootKey);
|
super.onCreatePreferences(savedInstanceState, rootKey);
|
||||||
addPreferencesFromResource(R.xml.preferences_appearance);
|
addPreferencesFromResource(R.xml.preferences_appearance);
|
||||||
Preferences prefs = getPreferences();
|
|
||||||
|
|
||||||
_groupsPreference = findPreference("pref_groups");
|
_groupsPreference = findPreference("pref_groups");
|
||||||
_groupsPreference.setOnPreferenceClickListener(preference -> {
|
_groupsPreference.setOnPreferenceClickListener(preference -> {
|
||||||
Intent intent = new Intent(getActivity(), GroupManagerActivity.class);
|
Intent intent = new Intent(getActivity(), GroupManagerActivity.class);
|
||||||
intent.putExtra("groups", new ArrayList<>(getVault().getGroups()));
|
intent.putExtra("groups", new ArrayList<>(_vaultManager.getVault().getGroups()));
|
||||||
startActivityForResult(intent, CODE_GROUPS);
|
startActivityForResult(intent, CODE_GROUPS);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
@ -42,23 +40,23 @@ public class AppearancePreferencesFragment extends PreferencesFragment {
|
||||||
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
|
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
|
||||||
.setTitle(R.string.preference_reset_usage_count)
|
.setTitle(R.string.preference_reset_usage_count)
|
||||||
.setMessage(R.string.preference_reset_usage_count_dialog)
|
.setMessage(R.string.preference_reset_usage_count_dialog)
|
||||||
.setPositiveButton(android.R.string.yes, (dialog, which) -> getPreferences().clearUsageCount())
|
.setPositiveButton(android.R.string.yes, (dialog, which) -> _prefs.clearUsageCount())
|
||||||
.setNegativeButton(android.R.string.no, null)
|
.setNegativeButton(android.R.string.no, null)
|
||||||
.create());
|
.create());
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
int currentTheme = prefs.getCurrentTheme().ordinal();
|
int currentTheme = _prefs.getCurrentTheme().ordinal();
|
||||||
Preference darkModePreference = findPreference("pref_dark_mode");
|
Preference darkModePreference = findPreference("pref_dark_mode");
|
||||||
darkModePreference.setSummary(String.format("%s: %s", getString(R.string.selected), getResources().getStringArray(R.array.theme_titles)[currentTheme]));
|
darkModePreference.setSummary(String.format("%s: %s", getString(R.string.selected), getResources().getStringArray(R.array.theme_titles)[currentTheme]));
|
||||||
darkModePreference.setOnPreferenceClickListener(preference -> {
|
darkModePreference.setOnPreferenceClickListener(preference -> {
|
||||||
int currentTheme1 = prefs.getCurrentTheme().ordinal();
|
int currentTheme1 = _prefs.getCurrentTheme().ordinal();
|
||||||
|
|
||||||
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
|
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
|
||||||
.setTitle(R.string.choose_theme)
|
.setTitle(R.string.choose_theme)
|
||||||
.setSingleChoiceItems(R.array.theme_titles, currentTheme1, (dialog, which) -> {
|
.setSingleChoiceItems(R.array.theme_titles, currentTheme1, (dialog, which) -> {
|
||||||
int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
|
int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
|
||||||
prefs.setCurrentTheme(Theme.fromInteger(i));
|
_prefs.setCurrentTheme(Theme.fromInteger(i));
|
||||||
|
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
|
|
||||||
|
@ -83,17 +81,17 @@ public class AppearancePreferencesFragment extends PreferencesFragment {
|
||||||
langPreference.setVisible(false);
|
langPreference.setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
int currentViewMode = prefs.getCurrentViewMode().ordinal();
|
int currentViewMode = _prefs.getCurrentViewMode().ordinal();
|
||||||
Preference viewModePreference = findPreference("pref_view_mode");
|
Preference viewModePreference = findPreference("pref_view_mode");
|
||||||
viewModePreference.setSummary(String.format("%s: %s", getString(R.string.selected), getResources().getStringArray(R.array.view_mode_titles)[currentViewMode]));
|
viewModePreference.setSummary(String.format("%s: %s", getString(R.string.selected), getResources().getStringArray(R.array.view_mode_titles)[currentViewMode]));
|
||||||
viewModePreference.setOnPreferenceClickListener(preference -> {
|
viewModePreference.setOnPreferenceClickListener(preference -> {
|
||||||
int currentViewMode1 = prefs.getCurrentViewMode().ordinal();
|
int currentViewMode1 = _prefs.getCurrentViewMode().ordinal();
|
||||||
|
|
||||||
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
|
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
|
||||||
.setTitle(R.string.choose_view_mode)
|
.setTitle(R.string.choose_view_mode)
|
||||||
.setSingleChoiceItems(R.array.view_mode_titles, currentViewMode1, (dialog, which) -> {
|
.setSingleChoiceItems(R.array.view_mode_titles, currentViewMode1, (dialog, which) -> {
|
||||||
int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
|
int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
|
||||||
prefs.setCurrentViewMode(ViewMode.fromInteger(i));
|
_prefs.setCurrentViewMode(ViewMode.fromInteger(i));
|
||||||
viewModePreference.setSummary(String.format("%s: %s", getString(R.string.selected), getResources().getStringArray(R.array.view_mode_titles)[i]));
|
viewModePreference.setSummary(String.format("%s: %s", getString(R.string.selected), getResources().getStringArray(R.array.view_mode_titles)[i]));
|
||||||
getResult().putExtra("needsRefresh", true);
|
getResult().putExtra("needsRefresh", true);
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
|
@ -133,12 +131,12 @@ public class AppearancePreferencesFragment extends PreferencesFragment {
|
||||||
|
|
||||||
HashSet<String> groups = new HashSet<>(data.getStringArrayListExtra("groups"));
|
HashSet<String> groups = new HashSet<>(data.getStringArrayListExtra("groups"));
|
||||||
|
|
||||||
for (VaultEntry entry : getVault().getEntries()) {
|
for (VaultEntry entry : _vaultManager.getVault().getEntries()) {
|
||||||
if (!groups.contains(entry.getGroup())) {
|
if (!groups.contains(entry.getGroup())) {
|
||||||
entry.setGroup(null);
|
entry.setGroup(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
saveVault();
|
saveAndBackupVault();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package com.beemdevelopment.aegis.ui.fragments;
|
package com.beemdevelopment.aegis.ui.fragments.preferences;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
@ -9,11 +9,9 @@ import android.widget.Toast;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
import androidx.preference.SwitchPreferenceCompat;
|
import androidx.preference.SwitchPreferenceCompat;
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.Preferences;
|
|
||||||
import com.beemdevelopment.aegis.R;
|
import com.beemdevelopment.aegis.R;
|
||||||
import com.beemdevelopment.aegis.ui.AegisActivity;
|
|
||||||
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
||||||
import com.beemdevelopment.aegis.vault.VaultManagerException;
|
import com.beemdevelopment.aegis.vault.VaultRepositoryException;
|
||||||
|
|
||||||
public class BackupsPreferencesFragment extends PreferencesFragment {
|
public class BackupsPreferencesFragment extends PreferencesFragment {
|
||||||
private SwitchPreferenceCompat _androidBackupsPreference;
|
private SwitchPreferenceCompat _androidBackupsPreference;
|
||||||
|
@ -32,14 +30,13 @@ public class BackupsPreferencesFragment extends PreferencesFragment {
|
||||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||||
super.onCreatePreferences(savedInstanceState, rootKey);
|
super.onCreatePreferences(savedInstanceState, rootKey);
|
||||||
addPreferencesFromResource(R.xml.preferences_backups);
|
addPreferencesFromResource(R.xml.preferences_backups);
|
||||||
Preferences prefs = getPreferences();
|
|
||||||
|
|
||||||
_backupsPreference = findPreference("pref_backups");
|
_backupsPreference = findPreference("pref_backups");
|
||||||
_backupsPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
_backupsPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
if ((boolean) newValue) {
|
if ((boolean) newValue) {
|
||||||
selectBackupsLocation();
|
selectBackupsLocation();
|
||||||
} else {
|
} else {
|
||||||
prefs.setIsBackupsEnabled(false);
|
_prefs.setIsBackupsEnabled(false);
|
||||||
updateBackupPreference();
|
updateBackupPreference();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,13 +45,13 @@ public class BackupsPreferencesFragment extends PreferencesFragment {
|
||||||
|
|
||||||
_androidBackupsPreference = findPreference("pref_android_backups");
|
_androidBackupsPreference = findPreference("pref_android_backups");
|
||||||
_androidBackupsPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
_androidBackupsPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
prefs.setIsAndroidBackupsEnabled((boolean) newValue);
|
_prefs.setIsAndroidBackupsEnabled((boolean) newValue);
|
||||||
updateBackupPreference();
|
updateBackupPreference();
|
||||||
getVault().androidBackupDataChanged();
|
_vaultManager.scheduleAndroidBackup();
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
Uri backupLocation = prefs.getBackupsLocation();
|
Uri backupLocation = _prefs.getBackupsLocation();
|
||||||
_backupsLocationPreference = findPreference("pref_backups_location");
|
_backupsLocationPreference = findPreference("pref_backups_location");
|
||||||
if (backupLocation != null) {
|
if (backupLocation != null) {
|
||||||
_backupsLocationPreference.setSummary(String.format("%s: %s", getString(R.string.pref_backups_location_summary), Uri.decode(backupLocation.toString())));
|
_backupsLocationPreference.setSummary(String.format("%s: %s", getString(R.string.pref_backups_location_summary), Uri.decode(backupLocation.toString())));
|
||||||
|
@ -66,11 +63,11 @@ public class BackupsPreferencesFragment extends PreferencesFragment {
|
||||||
|
|
||||||
_backupsTriggerPreference = findPreference("pref_backups_trigger");
|
_backupsTriggerPreference = findPreference("pref_backups_trigger");
|
||||||
_backupsTriggerPreference.setOnPreferenceClickListener(preference -> {
|
_backupsTriggerPreference.setOnPreferenceClickListener(preference -> {
|
||||||
if (prefs.isBackupsEnabled()) {
|
if (_prefs.isBackupsEnabled()) {
|
||||||
try {
|
try {
|
||||||
getVault().backup();
|
_vaultManager.scheduleBackup();
|
||||||
Toast.makeText(getActivity(), R.string.backup_successful, Toast.LENGTH_LONG).show();
|
Toast.makeText(getActivity(), R.string.backup_successful, Toast.LENGTH_LONG).show();
|
||||||
} catch (VaultManagerException e) {
|
} catch (VaultRepositoryException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
Dialogs.showErrorDialog(getContext(), R.string.backup_error, e);
|
Dialogs.showErrorDialog(getContext(), R.string.backup_error, e);
|
||||||
}
|
}
|
||||||
|
@ -79,12 +76,12 @@ public class BackupsPreferencesFragment extends PreferencesFragment {
|
||||||
});
|
});
|
||||||
|
|
||||||
_backupsVersionsPreference = findPreference("pref_backups_versions");
|
_backupsVersionsPreference = findPreference("pref_backups_versions");
|
||||||
_backupsVersionsPreference.setSummary(getResources().getQuantityString(R.plurals.pref_backups_versions_summary, prefs.getBackupsVersionCount(), prefs.getBackupsVersionCount()));
|
_backupsVersionsPreference.setSummary(getResources().getQuantityString(R.plurals.pref_backups_versions_summary, _prefs.getBackupsVersionCount(), _prefs.getBackupsVersionCount()));
|
||||||
_backupsVersionsPreference.setOnPreferenceClickListener(preference -> {
|
_backupsVersionsPreference.setOnPreferenceClickListener(preference -> {
|
||||||
Dialogs.showBackupVersionsPickerDialog(getActivity(), number -> {
|
Dialogs.showBackupVersionsPickerDialog(getActivity(), number -> {
|
||||||
number = number * 5 + 5;
|
number = number * 5 + 5;
|
||||||
prefs.setBackupsVersionCount(number);
|
_prefs.setBackupsVersionCount(number);
|
||||||
_backupsVersionsPreference.setSummary(getResources().getQuantityString(R.plurals.pref_backups_versions_summary, prefs.getBackupsVersionCount(), prefs.getBackupsVersionCount()));
|
_backupsVersionsPreference.setSummary(getResources().getQuantityString(R.plurals.pref_backups_versions_summary, _prefs.getBackupsVersionCount(), _prefs.getBackupsVersionCount()));
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
@ -106,18 +103,17 @@ public class BackupsPreferencesFragment extends PreferencesFragment {
|
||||||
int flags = Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
|
int flags = Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
|
||||||
getContext().getContentResolver().takePersistableUriPermission(data.getData(), flags);
|
getContext().getContentResolver().takePersistableUriPermission(data.getData(), flags);
|
||||||
|
|
||||||
Preferences prefs = getPreferences();
|
_prefs.setBackupsLocation(uri);
|
||||||
prefs.setBackupsLocation(uri);
|
_prefs.setIsBackupsEnabled(true);
|
||||||
prefs.setIsBackupsEnabled(true);
|
_prefs.setBackupsError(null);
|
||||||
prefs.setBackupsError(null);
|
|
||||||
_backupsLocationPreference.setSummary(String.format("%s: %s", getString(R.string.pref_backups_location_summary), Uri.decode(uri.toString())));
|
_backupsLocationPreference.setSummary(String.format("%s: %s", getString(R.string.pref_backups_location_summary), Uri.decode(uri.toString())));
|
||||||
updateBackupPreference();
|
updateBackupPreference();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateBackupPreference() {
|
private void updateBackupPreference() {
|
||||||
boolean encrypted = getVault().isEncryptionEnabled();
|
boolean encrypted = _vaultManager.getVault().isEncryptionEnabled();
|
||||||
boolean androidBackupEnabled = getPreferences().isAndroidBackupsEnabled() && encrypted;
|
boolean androidBackupEnabled = _prefs.isAndroidBackupsEnabled() && encrypted;
|
||||||
boolean backupEnabled = getPreferences().isBackupsEnabled() && encrypted;
|
boolean backupEnabled = _prefs.isBackupsEnabled() && encrypted;
|
||||||
_androidBackupsPreference.setChecked(androidBackupEnabled);
|
_androidBackupsPreference.setChecked(androidBackupEnabled);
|
||||||
_androidBackupsPreference.setEnabled(encrypted);
|
_androidBackupsPreference.setEnabled(encrypted);
|
||||||
_backupsPreference.setChecked(backupEnabled);
|
_backupsPreference.setChecked(backupEnabled);
|
||||||
|
@ -134,6 +130,6 @@ public class BackupsPreferencesFragment extends PreferencesFragment {
|
||||||
| Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
|
| Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
|
||||||
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
|
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
|
||||||
|
|
||||||
AegisActivity.Helper.startExtActivityForResult(this, intent, CODE_BACKUPS);
|
_vaultManager.startActivityForResult(this, intent, CODE_BACKUPS);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,15 +1,12 @@
|
||||||
package com.beemdevelopment.aegis.ui.fragments;
|
package com.beemdevelopment.aegis.ui.fragments.preferences;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.Preferences;
|
|
||||||
import com.beemdevelopment.aegis.R;
|
import com.beemdevelopment.aegis.R;
|
||||||
|
|
||||||
public class BehaviorPreferencesFragment extends PreferencesFragment {
|
public class BehaviorPreferencesFragment extends PreferencesFragment {
|
||||||
|
|
||||||
private Preferences _prefs;
|
|
||||||
private Preference _entryPausePreference;
|
private Preference _entryPausePreference;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -17,8 +14,6 @@ public class BehaviorPreferencesFragment extends PreferencesFragment {
|
||||||
super.onCreatePreferences(savedInstanceState, rootKey);
|
super.onCreatePreferences(savedInstanceState, rootKey);
|
||||||
addPreferencesFromResource(R.xml.preferences_behavior);
|
addPreferencesFromResource(R.xml.preferences_behavior);
|
||||||
|
|
||||||
_prefs = getPreferences();
|
|
||||||
|
|
||||||
Preference copyOnTapPreference = findPreference("pref_copy_on_tap");
|
Preference copyOnTapPreference = findPreference("pref_copy_on_tap");
|
||||||
copyOnTapPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
copyOnTapPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
getResult().putExtra("needsRefresh", true);
|
getResult().putExtra("needsRefresh", true);
|
|
@ -1,4 +1,4 @@
|
||||||
package com.beemdevelopment.aegis.ui.fragments;
|
package com.beemdevelopment.aegis.ui.fragments.preferences;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
@ -15,27 +15,35 @@ import androidx.fragment.app.Fragment;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.AegisApplication;
|
|
||||||
import com.beemdevelopment.aegis.R;
|
import com.beemdevelopment.aegis.R;
|
||||||
import com.beemdevelopment.aegis.helpers.FabScrollHelper;
|
import com.beemdevelopment.aegis.helpers.FabScrollHelper;
|
||||||
import com.beemdevelopment.aegis.icons.IconPack;
|
import com.beemdevelopment.aegis.icons.IconPack;
|
||||||
import com.beemdevelopment.aegis.icons.IconPackException;
|
import com.beemdevelopment.aegis.icons.IconPackException;
|
||||||
import com.beemdevelopment.aegis.icons.IconPackExistsException;
|
import com.beemdevelopment.aegis.icons.IconPackExistsException;
|
||||||
import com.beemdevelopment.aegis.icons.IconPackManager;
|
import com.beemdevelopment.aegis.icons.IconPackManager;
|
||||||
import com.beemdevelopment.aegis.ui.AegisActivity;
|
|
||||||
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
||||||
import com.beemdevelopment.aegis.ui.tasks.ImportIconPackTask;
|
import com.beemdevelopment.aegis.ui.tasks.ImportIconPackTask;
|
||||||
import com.beemdevelopment.aegis.ui.views.IconPackAdapter;
|
import com.beemdevelopment.aegis.ui.views.IconPackAdapter;
|
||||||
|
import com.beemdevelopment.aegis.vault.VaultManager;
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
public class IconPacksManagerFragment extends Fragment implements IconPackAdapter.Listener {
|
public class IconPacksManagerFragment extends Fragment implements IconPackAdapter.Listener {
|
||||||
private static final int CODE_IMPORT = 0;
|
private static final int CODE_IMPORT = 0;
|
||||||
|
|
||||||
private IconPackAdapter _adapter;
|
@Inject
|
||||||
private IconPackManager _iconPackManager;
|
IconPackManager _iconPackManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
VaultManager _vaultManager;
|
||||||
|
|
||||||
private View _iconPacksView;
|
private View _iconPacksView;
|
||||||
private RecyclerView _iconPacksRecyclerView;
|
private RecyclerView _iconPacksRecyclerView;
|
||||||
|
private IconPackAdapter _adapter;
|
||||||
private LinearLayout _noIconPacksView;
|
private LinearLayout _noIconPacksView;
|
||||||
private FabScrollHelper _fabScrollHelper;
|
private FabScrollHelper _fabScrollHelper;
|
||||||
|
|
||||||
|
@ -45,8 +53,6 @@ public class IconPacksManagerFragment extends Fragment implements IconPackAdapte
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
|
||||||
_iconPackManager = ((AegisApplication) getContext().getApplicationContext()).getIconPackManager();
|
|
||||||
|
|
||||||
FloatingActionButton fab = view.findViewById(R.id.fab);
|
FloatingActionButton fab = view.findViewById(R.id.fab);
|
||||||
fab.setOnClickListener(v -> startImportIconPack());
|
fab.setOnClickListener(v -> startImportIconPack());
|
||||||
_fabScrollHelper = new FabScrollHelper(fab);
|
_fabScrollHelper = new FabScrollHelper(fab);
|
||||||
|
@ -145,7 +151,7 @@ public class IconPacksManagerFragment extends Fragment implements IconPackAdapte
|
||||||
private void startImportIconPack() {
|
private void startImportIconPack() {
|
||||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
intent.setType("application/zip");
|
intent.setType("application/zip");
|
||||||
AegisActivity.Helper.startExtActivityForResult(this, intent, CODE_IMPORT);
|
_vaultManager.startActivityForResult(this, intent, CODE_IMPORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateEmptyState() {
|
private void updateEmptyState() {
|
|
@ -1,4 +1,4 @@
|
||||||
package com.beemdevelopment.aegis.ui.fragments;
|
package com.beemdevelopment.aegis.ui.fragments.preferences;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
@ -22,15 +22,14 @@ import com.beemdevelopment.aegis.BuildConfig;
|
||||||
import com.beemdevelopment.aegis.R;
|
import com.beemdevelopment.aegis.R;
|
||||||
import com.beemdevelopment.aegis.helpers.DropdownHelper;
|
import com.beemdevelopment.aegis.helpers.DropdownHelper;
|
||||||
import com.beemdevelopment.aegis.importers.DatabaseImporter;
|
import com.beemdevelopment.aegis.importers.DatabaseImporter;
|
||||||
import com.beemdevelopment.aegis.ui.AegisActivity;
|
|
||||||
import com.beemdevelopment.aegis.ui.ImportEntriesActivity;
|
import com.beemdevelopment.aegis.ui.ImportEntriesActivity;
|
||||||
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
||||||
import com.beemdevelopment.aegis.ui.tasks.ExportTask;
|
import com.beemdevelopment.aegis.ui.tasks.ExportTask;
|
||||||
import com.beemdevelopment.aegis.ui.tasks.ImportFileTask;
|
import com.beemdevelopment.aegis.ui.tasks.ImportFileTask;
|
||||||
import com.beemdevelopment.aegis.vault.VaultBackupManager;
|
import com.beemdevelopment.aegis.vault.VaultBackupManager;
|
||||||
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
|
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
|
||||||
import com.beemdevelopment.aegis.vault.VaultManager;
|
import com.beemdevelopment.aegis.vault.VaultRepository;
|
||||||
import com.beemdevelopment.aegis.vault.VaultManagerException;
|
import com.beemdevelopment.aegis.vault.VaultRepositoryException;
|
||||||
import com.beemdevelopment.aegis.vault.slots.Slot;
|
import com.beemdevelopment.aegis.vault.slots.Slot;
|
||||||
import com.beemdevelopment.aegis.vault.slots.SlotException;
|
import com.beemdevelopment.aegis.vault.slots.SlotException;
|
||||||
|
|
||||||
|
@ -61,7 +60,7 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
||||||
|
|
||||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
intent.setType("*/*");
|
intent.setType("*/*");
|
||||||
AegisActivity.Helper.startExtActivityForResult(this, intent, CODE_IMPORT_SELECT);
|
_vaultManager.startActivityForResult(this, intent, CODE_IMPORT_SELECT);
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
@ -184,7 +183,7 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
||||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
.setType(getExportMimeType(requestCode))
|
.setType(getExportMimeType(requestCode))
|
||||||
.putExtra(Intent.EXTRA_TITLE, fileInfo.toString());
|
.putExtra(Intent.EXTRA_TITLE, fileInfo.toString());
|
||||||
AegisActivity.Helper.startExtActivityForResult(this, intent, requestCode);
|
_vaultManager.startActivityForResult(this, intent, requestCode);
|
||||||
});
|
});
|
||||||
|
|
||||||
btnNeutral.setOnClickListener(v -> {
|
btnNeutral.setOnClickListener(v -> {
|
||||||
|
@ -209,7 +208,7 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
||||||
startExportVault(requestCode, cb -> {
|
startExportVault(requestCode, cb -> {
|
||||||
try (OutputStream stream = new FileOutputStream(file)) {
|
try (OutputStream stream = new FileOutputStream(file)) {
|
||||||
cb.exportVault(stream);
|
cb.exportVault(stream);
|
||||||
} catch (IOException | VaultManagerException e) {
|
} catch (IOException | VaultRepositoryException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
Dialogs.showErrorDialog(getContext(), R.string.exporting_vault_error, e);
|
Dialogs.showErrorDialog(getContext(), R.string.exporting_vault_error, e);
|
||||||
return;
|
return;
|
||||||
|
@ -221,7 +220,7 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
||||||
.setType(getExportMimeType(requestCode))
|
.setType(getExportMimeType(requestCode))
|
||||||
.putExtra(Intent.EXTRA_STREAM, uri);
|
.putExtra(Intent.EXTRA_STREAM, uri);
|
||||||
Intent chooser = Intent.createChooser(intent, getString(R.string.pref_export_summary));
|
Intent chooser = Intent.createChooser(intent, getString(R.string.pref_export_summary));
|
||||||
AegisActivity.Helper.startExtActivity(this, chooser);
|
_vaultManager.startActivity(this, chooser);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -239,11 +238,11 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
||||||
|
|
||||||
private static VaultBackupManager.FileInfo getExportFileInfo(int spinnerPos, boolean encrypt) {
|
private static VaultBackupManager.FileInfo getExportFileInfo(int spinnerPos, boolean encrypt) {
|
||||||
if (spinnerPos == 0) {
|
if (spinnerPos == 0) {
|
||||||
String filename = encrypt ? VaultManager.FILENAME_PREFIX_EXPORT : VaultManager.FILENAME_PREFIX_EXPORT_PLAIN;
|
String filename = encrypt ? VaultRepository.FILENAME_PREFIX_EXPORT : VaultRepository.FILENAME_PREFIX_EXPORT_PLAIN;
|
||||||
return new VaultBackupManager.FileInfo(filename);
|
return new VaultBackupManager.FileInfo(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new VaultBackupManager.FileInfo(VaultManager.FILENAME_PREFIX_EXPORT_URI, "txt");
|
return new VaultBackupManager.FileInfo(VaultRepository.FILENAME_PREFIX_EXPORT_URI, "txt");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getExportMimeType(int requestCode) {
|
private static String getExportMimeType(int requestCode) {
|
||||||
|
@ -262,8 +261,8 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
||||||
private void startExportVault(int requestCode, StartExportCallback cb) {
|
private void startExportVault(int requestCode, StartExportCallback cb) {
|
||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
case CODE_EXPORT:
|
case CODE_EXPORT:
|
||||||
if (getVault().isEncryptionEnabled()) {
|
if (_vaultManager.getVault().isEncryptionEnabled()) {
|
||||||
cb.exportVault(stream -> getVault().export(stream));
|
cb.exportVault(stream -> _vaultManager.getVault().export(stream));
|
||||||
} else {
|
} else {
|
||||||
Dialogs.showSetPasswordDialog(getActivity(), new Dialogs.SlotListener() {
|
Dialogs.showSetPasswordDialog(getActivity(), new Dialogs.SlotListener() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -278,7 +277,7 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cb.exportVault(stream -> getVault().export(stream, creds));
|
cb.exportVault(stream -> _vaultManager.getVault().export(stream, creds));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -289,10 +288,10 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CODE_EXPORT_PLAIN:
|
case CODE_EXPORT_PLAIN:
|
||||||
cb.exportVault((stream) -> getVault().export(stream, null));
|
cb.exportVault((stream) -> _vaultManager.getVault().export(stream, null));
|
||||||
break;
|
break;
|
||||||
case CODE_EXPORT_GOOGLE_URI:
|
case CODE_EXPORT_GOOGLE_URI:
|
||||||
cb.exportVault((stream) -> getVault().exportGoogleUris(stream));
|
cb.exportVault((stream) -> _vaultManager.getVault().exportGoogleUris(stream));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -307,12 +306,12 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
||||||
File file;
|
File file;
|
||||||
OutputStream outStream = null;
|
OutputStream outStream = null;
|
||||||
try {
|
try {
|
||||||
file = File.createTempFile(VaultManager.FILENAME_PREFIX_EXPORT + "-", ".json", getExportCacheDir());
|
file = File.createTempFile(VaultRepository.FILENAME_PREFIX_EXPORT + "-", ".json", getExportCacheDir());
|
||||||
outStream = new FileOutputStream(file);
|
outStream = new FileOutputStream(file);
|
||||||
cb.exportVault(outStream);
|
cb.exportVault(outStream);
|
||||||
|
|
||||||
new ExportTask(getContext(), new ExportResultListener()).execute(getLifecycle(), new ExportTask.Params(file, uri));
|
new ExportTask(getContext(), new ExportResultListener()).execute(getLifecycle(), new ExportTask.Params(file, uri));
|
||||||
} catch (VaultManagerException | IOException e) {
|
} catch (VaultRepositoryException | IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
Dialogs.showErrorDialog(getContext(), R.string.exporting_vault_error, e);
|
Dialogs.showErrorDialog(getContext(), R.string.exporting_vault_error, e);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -350,7 +349,7 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface FinishExportCallback {
|
private interface FinishExportCallback {
|
||||||
void exportVault(OutputStream stream) throws IOException, VaultManagerException;
|
void exportVault(OutputStream stream) throws IOException, VaultRepositoryException;
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface StartExportCallback {
|
private interface StartExportCallback {
|
|
@ -1,4 +1,4 @@
|
||||||
package com.beemdevelopment.aegis.ui.fragments;
|
package com.beemdevelopment.aegis.ui.fragments.preferences;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package com.beemdevelopment.aegis.ui.fragments;
|
package com.beemdevelopment.aegis.ui.fragments.preferences;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
@ -7,13 +7,17 @@ import android.os.Bundle;
|
||||||
import androidx.annotation.CallSuper;
|
import androidx.annotation.CallSuper;
|
||||||
import androidx.preference.PreferenceFragmentCompat;
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.AegisApplication;
|
|
||||||
import com.beemdevelopment.aegis.Preferences;
|
import com.beemdevelopment.aegis.Preferences;
|
||||||
import com.beemdevelopment.aegis.R;
|
import com.beemdevelopment.aegis.R;
|
||||||
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
||||||
import com.beemdevelopment.aegis.vault.VaultManager;
|
import com.beemdevelopment.aegis.vault.VaultManager;
|
||||||
import com.beemdevelopment.aegis.vault.VaultManagerException;
|
import com.beemdevelopment.aegis.vault.VaultRepositoryException;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
public abstract class PreferencesFragment extends PreferenceFragmentCompat {
|
public abstract class PreferencesFragment extends PreferenceFragmentCompat {
|
||||||
// activity request codes
|
// activity request codes
|
||||||
public static final int CODE_IMPORT_SELECT = 0;
|
public static final int CODE_IMPORT_SELECT = 0;
|
||||||
|
@ -25,18 +29,17 @@ public abstract class PreferencesFragment extends PreferenceFragmentCompat {
|
||||||
public static final int CODE_EXPORT_GOOGLE_URI = 7;
|
public static final int CODE_EXPORT_GOOGLE_URI = 7;
|
||||||
public static final int CODE_BACKUPS = 8;
|
public static final int CODE_BACKUPS = 8;
|
||||||
|
|
||||||
private AegisApplication _app;
|
|
||||||
private Intent _result;
|
private Intent _result;
|
||||||
private Preferences _prefs;
|
|
||||||
private VaultManager _vault;
|
@Inject
|
||||||
|
Preferences _prefs;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
VaultManager _vaultManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@CallSuper
|
@CallSuper
|
||||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||||
_app = (AegisApplication) getActivity().getApplication();
|
|
||||||
_prefs = _app.getPreferences();
|
|
||||||
_vault = _app.getVaultManager();
|
|
||||||
|
|
||||||
setResult(new Intent());
|
setResult(new Intent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,22 +65,10 @@ public abstract class PreferencesFragment extends PreferenceFragmentCompat {
|
||||||
getActivity().setResult(Activity.RESULT_OK, _result);
|
getActivity().setResult(Activity.RESULT_OK, _result);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AegisApplication getApp() {
|
protected boolean saveAndBackupVault() {
|
||||||
return _app;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Preferences getPreferences() {
|
|
||||||
return _prefs;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected VaultManager getVault() {
|
|
||||||
return _vault;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean saveVault() {
|
|
||||||
try {
|
try {
|
||||||
_vault.save(true);
|
_vaultManager.saveAndBackup();
|
||||||
} catch (VaultManagerException e) {
|
} catch (VaultRepositoryException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
Dialogs.showErrorDialog(getContext(), R.string.saving_error, e);
|
Dialogs.showErrorDialog(getContext(), R.string.saving_error, e);
|
||||||
return false;
|
return false;
|
|
@ -1,4 +1,4 @@
|
||||||
package com.beemdevelopment.aegis.ui.fragments;
|
package com.beemdevelopment.aegis.ui.fragments.preferences;
|
||||||
|
|
||||||
import static android.text.TextUtils.isDigitsOnly;
|
import static android.text.TextUtils.isDigitsOnly;
|
||||||
|
|
||||||
|
@ -23,13 +23,12 @@ import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
|
||||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
|
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
|
||||||
import com.beemdevelopment.aegis.helpers.BiometricSlotInitializer;
|
import com.beemdevelopment.aegis.helpers.BiometricSlotInitializer;
|
||||||
import com.beemdevelopment.aegis.helpers.BiometricsHelper;
|
import com.beemdevelopment.aegis.helpers.BiometricsHelper;
|
||||||
import com.beemdevelopment.aegis.services.NotificationService;
|
|
||||||
import com.beemdevelopment.aegis.ui.SlotManagerActivity;
|
import com.beemdevelopment.aegis.ui.SlotManagerActivity;
|
||||||
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
||||||
import com.beemdevelopment.aegis.ui.preferences.SwitchPreference;
|
import com.beemdevelopment.aegis.ui.preferences.SwitchPreference;
|
||||||
import com.beemdevelopment.aegis.ui.tasks.PasswordSlotDecryptTask;
|
import com.beemdevelopment.aegis.ui.tasks.PasswordSlotDecryptTask;
|
||||||
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
|
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
|
||||||
import com.beemdevelopment.aegis.vault.VaultManagerException;
|
import com.beemdevelopment.aegis.vault.VaultRepositoryException;
|
||||||
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;
|
||||||
import com.beemdevelopment.aegis.vault.slots.Slot;
|
import com.beemdevelopment.aegis.vault.slots.Slot;
|
||||||
|
@ -80,10 +79,10 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
});
|
});
|
||||||
|
|
||||||
Preference tapToRevealTimePreference = findPreference("pref_tap_to_reveal_time");
|
Preference tapToRevealTimePreference = findPreference("pref_tap_to_reveal_time");
|
||||||
tapToRevealTimePreference.setSummary(getPreferences().getTapToRevealTime() + " seconds");
|
tapToRevealTimePreference.setSummary(_prefs.getTapToRevealTime() + " seconds");
|
||||||
tapToRevealTimePreference.setOnPreferenceClickListener(preference -> {
|
tapToRevealTimePreference.setOnPreferenceClickListener(preference -> {
|
||||||
Dialogs.showNumberPickerDialog(getActivity(), number -> {
|
Dialogs.showNumberPickerDialog(getActivity(), number -> {
|
||||||
getPreferences().setTapToRevealTime(number);
|
_prefs.setTapToRevealTime(number);
|
||||||
tapToRevealTimePreference.setSummary(number + " seconds");
|
tapToRevealTimePreference.setSummary(number + " seconds");
|
||||||
getResult().putExtra("needsRefresh", true);
|
getResult().putExtra("needsRefresh", true);
|
||||||
});
|
});
|
||||||
|
@ -92,7 +91,7 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
|
|
||||||
_encryptionPreference = findPreference("pref_encryption");
|
_encryptionPreference = findPreference("pref_encryption");
|
||||||
_encryptionPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
_encryptionPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
if (!getVault().isEncryptionEnabled()) {
|
if (!_vaultManager.getVault().isEncryptionEnabled()) {
|
||||||
Dialogs.showSetPasswordDialog(getActivity(), new EnableEncryptionListener());
|
Dialogs.showSetPasswordDialog(getActivity(), new EnableEncryptionListener());
|
||||||
} else {
|
} else {
|
||||||
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
|
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
|
||||||
|
@ -100,24 +99,15 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
.setMessage(getText(R.string.disable_encryption_description))
|
.setMessage(getText(R.string.disable_encryption_description))
|
||||||
.setPositiveButton(android.R.string.yes, (dialog, which) -> {
|
.setPositiveButton(android.R.string.yes, (dialog, which) -> {
|
||||||
try {
|
try {
|
||||||
getVault().disableEncryption();
|
_vaultManager.disableEncryption();
|
||||||
} catch (VaultManagerException e) {
|
} catch (VaultRepositoryException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
Dialogs.showErrorDialog(getContext(), R.string.disable_encryption_error, e);
|
Dialogs.showErrorDialog(getContext(), R.string.disable_encryption_error, e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear the KeyStore
|
_prefs.setIsBackupsEnabled(false);
|
||||||
try {
|
_prefs.setIsAndroidBackupsEnabled(false);
|
||||||
KeyStoreHandle handle = new KeyStoreHandle();
|
|
||||||
handle.clear();
|
|
||||||
} catch (KeyStoreHandleException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
getActivity().stopService(new Intent(getActivity(), NotificationService.class));
|
|
||||||
getPreferences().setIsBackupsEnabled(false);
|
|
||||||
getPreferences().setIsAndroidBackupsEnabled(false);
|
|
||||||
updateEncryptionPreferences();
|
updateEncryptionPreferences();
|
||||||
})
|
})
|
||||||
.setNegativeButton(android.R.string.no, null)
|
.setNegativeButton(android.R.string.no, null)
|
||||||
|
@ -129,7 +119,7 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
|
|
||||||
_biometricsPreference = findPreference("pref_biometrics");
|
_biometricsPreference = findPreference("pref_biometrics");
|
||||||
_biometricsPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
_biometricsPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
VaultFileCredentials creds = getVault().getCredentials();
|
VaultFileCredentials creds = _vaultManager.getVault().getCredentials();
|
||||||
SlotList slots = creds.getSlots();
|
SlotList slots = creds.getSlots();
|
||||||
|
|
||||||
if (!slots.has(BiometricSlot.class)) {
|
if (!slots.has(BiometricSlot.class)) {
|
||||||
|
@ -145,7 +135,7 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
// remove the biometric slot
|
// remove the biometric slot
|
||||||
BiometricSlot slot = slots.find(BiometricSlot.class);
|
BiometricSlot slot = slots.find(BiometricSlot.class);
|
||||||
slots.remove(slot);
|
slots.remove(slot);
|
||||||
getVault().setCredentials(creds);
|
_vaultManager.getVault().setCredentials(creds);
|
||||||
|
|
||||||
// remove the KeyStore key
|
// remove the KeyStore key
|
||||||
try {
|
try {
|
||||||
|
@ -155,7 +145,7 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
saveVault();
|
saveAndBackupVault();
|
||||||
updateEncryptionPreferences();
|
updateEncryptionPreferences();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,7 +161,7 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
_slotsPreference = findPreference("pref_slots");
|
_slotsPreference = findPreference("pref_slots");
|
||||||
_slotsPreference.setOnPreferenceClickListener(preference -> {
|
_slotsPreference.setOnPreferenceClickListener(preference -> {
|
||||||
Intent intent = new Intent(getActivity(), SlotManagerActivity.class);
|
Intent intent = new Intent(getActivity(), SlotManagerActivity.class);
|
||||||
intent.putExtra("creds", getVault().getCredentials());
|
intent.putExtra("creds", _vaultManager.getVault().getCredentials());
|
||||||
startActivityForResult(intent, CODE_SLOTS);
|
startActivityForResult(intent, CODE_SLOTS);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
@ -184,7 +174,7 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
|
|
||||||
Dialogs.showPasswordInputDialog(getActivity(), R.string.set_password_confirm, R.string.pin_keyboard_description, password -> {
|
Dialogs.showPasswordInputDialog(getActivity(), R.string.set_password_confirm, R.string.pin_keyboard_description, password -> {
|
||||||
if (isDigitsOnly(new String(password))) {
|
if (isDigitsOnly(new String(password))) {
|
||||||
List<PasswordSlot> slots = getVault().getCredentials().getSlots().findAll(PasswordSlot.class);
|
List<PasswordSlot> slots = _vaultManager.getVault().getCredentials().getSlots().findAll(PasswordSlot.class);
|
||||||
PasswordSlotDecryptTask.Params params = new PasswordSlotDecryptTask.Params(slots, password);
|
PasswordSlotDecryptTask.Params params = new PasswordSlotDecryptTask.Params(slots, password);
|
||||||
PasswordSlotDecryptTask task = new PasswordSlotDecryptTask(getActivity(), new PasswordConfirmationListener());
|
PasswordSlotDecryptTask task = new PasswordSlotDecryptTask(getActivity(), new PasswordConfirmationListener());
|
||||||
task.execute(getLifecycle(), params);
|
task.execute(getLifecycle(), params);
|
||||||
|
@ -210,7 +200,7 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
final String[] textItems = getResources().getStringArray(R.array.pref_auto_lock_types);
|
final String[] textItems = getResources().getStringArray(R.array.pref_auto_lock_types);
|
||||||
final boolean[] checkedItems = new boolean[items.length];
|
final boolean[] checkedItems = new boolean[items.length];
|
||||||
for (int i = 0; i < items.length; i++) {
|
for (int i = 0; i < items.length; i++) {
|
||||||
checkedItems[i] = getPreferences().isAutoLockTypeEnabled(items[i]);
|
checkedItems[i] = _prefs.isAutoLockTypeEnabled(items[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
|
||||||
|
@ -224,7 +214,7 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getPreferences().setAutoLockMask(autoLock);
|
_prefs.setAutoLockMask(autoLock);
|
||||||
_autoLockPreference.setSummary(getAutoLockSummary());
|
_autoLockPreference.setSummary(getAutoLockSummary());
|
||||||
})
|
})
|
||||||
.setNegativeButton(android.R.string.cancel, null);
|
.setNegativeButton(android.R.string.cancel, null);
|
||||||
|
@ -236,7 +226,7 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
_passwordReminderPreference = findPreference("pref_password_reminder_freq");
|
_passwordReminderPreference = findPreference("pref_password_reminder_freq");
|
||||||
_passwordReminderPreference.setSummary(getPasswordReminderSummary());
|
_passwordReminderPreference.setSummary(getPasswordReminderSummary());
|
||||||
_passwordReminderPreference.setOnPreferenceClickListener((preference) -> {
|
_passwordReminderPreference.setOnPreferenceClickListener((preference) -> {
|
||||||
final PassReminderFreq currFreq = getPreferences().getPasswordReminderFrequency();
|
final PassReminderFreq currFreq = _prefs.getPasswordReminderFrequency();
|
||||||
final PassReminderFreq[] items = PassReminderFreq.values();
|
final PassReminderFreq[] items = PassReminderFreq.values();
|
||||||
final String[] textItems = Arrays.stream(items)
|
final String[] textItems = Arrays.stream(items)
|
||||||
.map(f -> getString(f.getStringRes()))
|
.map(f -> getString(f.getStringRes()))
|
||||||
|
@ -247,7 +237,7 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
.setSingleChoiceItems(textItems, currFreq.ordinal(), (dialog, which) -> {
|
.setSingleChoiceItems(textItems, currFreq.ordinal(), (dialog, which) -> {
|
||||||
int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
|
int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
|
||||||
PassReminderFreq freq = PassReminderFreq.fromInteger(i);
|
PassReminderFreq freq = PassReminderFreq.fromInteger(i);
|
||||||
getPreferences().setPasswordReminderFrequency(freq);
|
_prefs.setPasswordReminderFrequency(freq);
|
||||||
_passwordReminderPreference.setSummary(getPasswordReminderSummary());
|
_passwordReminderPreference.setSummary(getPasswordReminderSummary());
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
})
|
})
|
||||||
|
@ -270,13 +260,13 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
VaultFileCredentials creds = (VaultFileCredentials) data.getSerializableExtra("creds");
|
VaultFileCredentials creds = (VaultFileCredentials) data.getSerializableExtra("creds");
|
||||||
getVault().setCredentials(creds);
|
_vaultManager.getVault().setCredentials(creds);
|
||||||
saveVault();
|
saveAndBackupVault();
|
||||||
updateEncryptionPreferences();
|
updateEncryptionPreferences();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateEncryptionPreferences() {
|
private void updateEncryptionPreferences() {
|
||||||
boolean encrypted = getVault().isEncryptionEnabled();
|
boolean encrypted = _vaultManager.getVault().isEncryptionEnabled();
|
||||||
_encryptionPreference.setChecked(encrypted, true);
|
_encryptionPreference.setChecked(encrypted, true);
|
||||||
_setPasswordPreference.setVisible(encrypted);
|
_setPasswordPreference.setVisible(encrypted);
|
||||||
_biometricsPreference.setVisible(encrypted);
|
_biometricsPreference.setVisible(encrypted);
|
||||||
|
@ -285,7 +275,7 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
_pinKeyboardPreference.setVisible(encrypted);
|
_pinKeyboardPreference.setVisible(encrypted);
|
||||||
|
|
||||||
if (encrypted) {
|
if (encrypted) {
|
||||||
SlotList slots = getVault().getCredentials().getSlots();
|
SlotList slots = _vaultManager.getVault().getCredentials().getSlots();
|
||||||
boolean multiPassword = slots.findAll(PasswordSlot.class).size() > 1;
|
boolean multiPassword = slots.findAll(PasswordSlot.class).size() > 1;
|
||||||
boolean multiBio = slots.findAll(BiometricSlot.class).size() > 1;
|
boolean multiBio = slots.findAll(BiometricSlot.class).size() > 1;
|
||||||
boolean showSlots = BuildConfig.DEBUG || multiPassword || multiBio;
|
boolean showSlots = BuildConfig.DEBUG || multiPassword || multiBio;
|
||||||
|
@ -305,7 +295,7 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getPasswordReminderSummary() {
|
private String getPasswordReminderSummary() {
|
||||||
PassReminderFreq freq = getPreferences().getPasswordReminderFrequency();
|
PassReminderFreq freq = _prefs.getPasswordReminderFrequency();
|
||||||
if (freq == PassReminderFreq.NEVER) {
|
if (freq == PassReminderFreq.NEVER) {
|
||||||
return getString(R.string.pref_password_reminder_summary_disabled);
|
return getString(R.string.pref_password_reminder_summary_disabled);
|
||||||
}
|
}
|
||||||
|
@ -320,7 +310,7 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
|
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
for (int i = 0; i < settings.length; i++) {
|
for (int i = 0; i < settings.length; i++) {
|
||||||
if (getPreferences().isAutoLockTypeEnabled(settings[i])) {
|
if (_prefs.isAutoLockTypeEnabled(settings[i])) {
|
||||||
if (builder.length() != 0) {
|
if (builder.length() != 0) {
|
||||||
builder.append(", ");
|
builder.append(", ");
|
||||||
}
|
}
|
||||||
|
@ -339,7 +329,7 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
private class SetPasswordListener implements Dialogs.SlotListener {
|
private class SetPasswordListener implements Dialogs.SlotListener {
|
||||||
@Override
|
@Override
|
||||||
public void onSlotResult(Slot slot, Cipher cipher) {
|
public void onSlotResult(Slot slot, Cipher cipher) {
|
||||||
VaultFileCredentials creds = getVault().getCredentials();
|
VaultFileCredentials creds = _vaultManager.getVault().getCredentials();
|
||||||
SlotList slots = creds.getSlots();
|
SlotList slots = creds.getSlots();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -359,10 +349,10 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
getVault().setCredentials(creds);
|
_vaultManager.getVault().setCredentials(creds);
|
||||||
saveVault();
|
saveAndBackupVault();
|
||||||
|
|
||||||
if (getPreferences().isPinKeyboardEnabled()) {
|
if (_prefs.isPinKeyboardEnabled()) {
|
||||||
_pinKeyboardPreference.setChecked(false);
|
_pinKeyboardPreference.setChecked(false);
|
||||||
Toast.makeText(getContext(), R.string.pin_keyboard_disabled, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), R.string.pin_keyboard_disabled, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
|
@ -379,7 +369,7 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
private class RegisterBiometricsListener implements BiometricSlotInitializer.Listener {
|
private class RegisterBiometricsListener implements BiometricSlotInitializer.Listener {
|
||||||
@Override
|
@Override
|
||||||
public void onInitializeSlot(BiometricSlot slot, Cipher cipher) {
|
public void onInitializeSlot(BiometricSlot slot, Cipher cipher) {
|
||||||
VaultFileCredentials creds = getVault().getCredentials();
|
VaultFileCredentials creds = _vaultManager.getVault().getCredentials();
|
||||||
try {
|
try {
|
||||||
slot.setKey(creds.getKey(), cipher);
|
slot.setKey(creds.getKey(), cipher);
|
||||||
} catch (SlotException e) {
|
} catch (SlotException e) {
|
||||||
|
@ -388,9 +378,9 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
creds.getSlots().add(slot);
|
creds.getSlots().add(slot);
|
||||||
getVault().setCredentials(creds);
|
_vaultManager.getVault().setCredentials(creds);
|
||||||
|
|
||||||
saveVault();
|
saveAndBackupVault();
|
||||||
updateEncryptionPreferences();
|
updateEncryptionPreferences();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -410,13 +400,12 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
try {
|
try {
|
||||||
slot.setKey(creds.getKey(), cipher);
|
slot.setKey(creds.getKey(), cipher);
|
||||||
creds.getSlots().add(slot);
|
creds.getSlots().add(slot);
|
||||||
getVault().enableEncryption(creds);
|
_vaultManager.enableEncryption(creds);
|
||||||
} catch (VaultManagerException | SlotException e) {
|
} catch (VaultRepositoryException | SlotException e) {
|
||||||
onException(e);
|
onException(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
getActivity().startService(new Intent(getActivity(), NotificationService.class));
|
|
||||||
_pinKeyboardPreference.setChecked(false);
|
_pinKeyboardPreference.setChecked(false);
|
||||||
updateEncryptionPreferences();
|
updateEncryptionPreferences();
|
||||||
}
|
}
|
|
@ -2,7 +2,6 @@ package com.beemdevelopment.aegis.ui.views;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.ColorStateList;
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
@ -412,7 +411,7 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
||||||
chipGroup.removeAllViews();
|
chipGroup.removeAllViews();
|
||||||
|
|
||||||
for (String group : _groups) {
|
for (String group : _groups) {
|
||||||
Chip chip = (Chip) this.getLayoutInflater().inflate(R.layout.chip_material, null, false);
|
Chip chip = (Chip) getLayoutInflater().inflate(R.layout.chip_material, null, false);
|
||||||
chip.setText(group);
|
chip.setText(group);
|
||||||
chip.setCheckable(true);
|
chip.setCheckable(true);
|
||||||
chip.setChecked(_groupFilter != null && _groupFilter.contains(group));
|
chip.setChecked(_groupFilter != null && _groupFilter.contains(group));
|
||||||
|
|
|
@ -48,24 +48,19 @@ public class VaultBackupManager {
|
||||||
_executor = Executors.newSingleThreadExecutor();
|
_executor = Executors.newSingleThreadExecutor();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void destroy() {
|
|
||||||
Log.i(TAG, "Shutting down backup manager thread");
|
|
||||||
_executor.shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void scheduleBackup(File tempFile, Uri dirUri, int versionsToKeep) {
|
public void scheduleBackup(File tempFile, Uri dirUri, int versionsToKeep) {
|
||||||
_executor.execute(() -> {
|
_executor.execute(() -> {
|
||||||
try {
|
try {
|
||||||
createBackup(tempFile, dirUri, versionsToKeep);
|
createBackup(tempFile, dirUri, versionsToKeep);
|
||||||
_prefs.setBackupsError(null);
|
_prefs.setBackupsError(null);
|
||||||
} catch (VaultManagerException e) {
|
} catch (VaultRepositoryException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
_prefs.setBackupsError(e);
|
_prefs.setBackupsError(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createBackup(File tempFile, Uri dirUri, int versionsToKeep) throws VaultManagerException {
|
private void createBackup(File tempFile, Uri dirUri, int versionsToKeep) throws VaultRepositoryException {
|
||||||
FileInfo fileInfo = new FileInfo(FILENAME_PREFIX);
|
FileInfo fileInfo = new FileInfo(FILENAME_PREFIX);
|
||||||
DocumentFile dir = DocumentFile.fromTreeUri(_context, dirUri);
|
DocumentFile dir = DocumentFile.fromTreeUri(_context, dirUri);
|
||||||
|
|
||||||
|
@ -73,28 +68,28 @@ public class VaultBackupManager {
|
||||||
Log.i(TAG, String.format("Creating backup at %s: %s", Uri.decode(dir.getUri().toString()), fileInfo.toString()));
|
Log.i(TAG, String.format("Creating backup at %s: %s", Uri.decode(dir.getUri().toString()), fileInfo.toString()));
|
||||||
|
|
||||||
if (!hasPermissionsAt(dirUri)) {
|
if (!hasPermissionsAt(dirUri)) {
|
||||||
throw new VaultManagerException("No persisted URI permissions");
|
throw new VaultRepositoryException("No persisted URI permissions");
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we create a file with a name that already exists, SAF will append a number
|
// If we create a file with a name that already exists, SAF will append a number
|
||||||
// to the filename and write to that instead. We can't overwrite existing files, so
|
// to the filename and write to that instead. We can't overwrite existing files, so
|
||||||
// just avoid that altogether by checking beforehand.
|
// just avoid that altogether by checking beforehand.
|
||||||
if (dir.findFile(fileInfo.toString()) != null) {
|
if (dir.findFile(fileInfo.toString()) != null) {
|
||||||
throw new VaultManagerException("Backup file already exists");
|
throw new VaultRepositoryException("Backup file already exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
DocumentFile file = dir.createFile("application/json", fileInfo.toString());
|
DocumentFile file = dir.createFile("application/json", fileInfo.toString());
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
throw new VaultManagerException("createFile returned null");
|
throw new VaultRepositoryException("createFile returned null");
|
||||||
}
|
}
|
||||||
|
|
||||||
try (FileInputStream inStream = new FileInputStream(tempFile);
|
try (FileInputStream inStream = new FileInputStream(tempFile);
|
||||||
OutputStream outStream = _context.getContentResolver().openOutputStream(file.getUri())) {
|
OutputStream outStream = _context.getContentResolver().openOutputStream(file.getUri())) {
|
||||||
IOUtils.copy(inStream, outStream);
|
IOUtils.copy(inStream, outStream);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new VaultManagerException(e);
|
throw new VaultRepositoryException(e);
|
||||||
}
|
}
|
||||||
} catch (VaultManagerException e) {
|
} catch (VaultRepositoryException e) {
|
||||||
Log.e(TAG, String.format("Unable to create backup: %s", e.toString()));
|
Log.e(TAG, String.format("Unable to create backup: %s", e.toString()));
|
||||||
throw e;
|
throw e;
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -252,7 +247,7 @@ public class VaultBackupManager {
|
||||||
int posIndex = pos.getIndex();
|
int posIndex = pos.getIndex();
|
||||||
Date d = super.parse(text, pos);
|
Date d = super.parse(text, pos);
|
||||||
if (!isLenient() && d != null) {
|
if (!isLenient() && d != null) {
|
||||||
String format = this.format(d);
|
String format = format(d);
|
||||||
if (posIndex + format.length() != text.length() ||
|
if (posIndex + format.length() != text.length() ||
|
||||||
!text.endsWith(format)) {
|
!text.endsWith(format)) {
|
||||||
d = null; // Not exact match
|
d = null; // Not exact match
|
||||||
|
|
|
@ -1,207 +1,187 @@
|
||||||
package com.beemdevelopment.aegis.vault;
|
package com.beemdevelopment.aegis.vault;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.app.backup.BackupManager;
|
import android.app.backup.BackupManager;
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
import androidx.core.util.AtomicFile;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.Preferences;
|
import com.beemdevelopment.aegis.Preferences;
|
||||||
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
|
import com.beemdevelopment.aegis.R;
|
||||||
import com.beemdevelopment.aegis.util.IOUtils;
|
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
|
||||||
|
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
|
||||||
|
import com.beemdevelopment.aegis.services.NotificationService;
|
||||||
|
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
||||||
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.util.ArrayList;
|
||||||
import java.io.OutputStream;
|
import java.util.List;
|
||||||
import java.io.PrintStream;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.text.Collator;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class VaultManager {
|
public class VaultManager {
|
||||||
public static final String FILENAME = "aegis.json";
|
private final Context _context;
|
||||||
public static final String FILENAME_PREFIX_EXPORT = "aegis-export";
|
private final Preferences _prefs;
|
||||||
public static final String FILENAME_PREFIX_EXPORT_PLAIN = "aegis-export-plain";
|
|
||||||
public static final String FILENAME_PREFIX_EXPORT_URI = "aegis-export-uri";
|
|
||||||
|
|
||||||
private Vault _vault;
|
private VaultFile _vaultFile;
|
||||||
private VaultFileCredentials _creds;
|
private VaultRepositoryException _vaultFileError;
|
||||||
|
private VaultRepository _repo;
|
||||||
|
|
||||||
private Context _context;
|
private final VaultBackupManager _backups;
|
||||||
private Preferences _prefs;
|
private final BackupManager _androidBackups;
|
||||||
private VaultBackupManager _backups;
|
|
||||||
private BackupManager _androidBackups;
|
|
||||||
|
|
||||||
public VaultManager(Context context, Vault vault, VaultFileCredentials creds) {
|
private final List<LockListener> _lockListeners;
|
||||||
|
private boolean _blockAutoLock;
|
||||||
|
|
||||||
|
public VaultManager(@NonNull Context context) {
|
||||||
_context = context;
|
_context = context;
|
||||||
_prefs = new Preferences(context);
|
_prefs = new Preferences(_context);
|
||||||
_backups = new VaultBackupManager(context);
|
_backups = new VaultBackupManager(_context);
|
||||||
_androidBackups = new BackupManager(context);
|
_androidBackups = new BackupManager(context);
|
||||||
_vault = vault;
|
_lockListeners = new ArrayList<>();
|
||||||
_creds = creds;
|
loadVaultFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
public VaultManager(Context context, Vault vault) {
|
private void loadVaultFile() {
|
||||||
this(context, vault, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AtomicFile getAtomicFile(Context context) {
|
|
||||||
return new AtomicFile(new File(context.getFilesDir(), FILENAME));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean fileExists(Context context) {
|
|
||||||
File file = getAtomicFile(context).getBaseFile();
|
|
||||||
return file.exists() && file.isFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void deleteFile(Context context) {
|
|
||||||
getAtomicFile(context).delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static VaultFile readVaultFile(Context context) throws VaultManagerException {
|
|
||||||
AtomicFile file = getAtomicFile(context);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
byte[] fileBytes = file.readFully();
|
_vaultFile = VaultRepository.readVaultFile(_context);
|
||||||
return VaultFile.fromBytes(fileBytes);
|
} catch (VaultRepositoryException e) {
|
||||||
} catch (IOException | VaultFileException e) {
|
e.printStackTrace();
|
||||||
throw new VaultManagerException(e);
|
if (!(e.getCause() instanceof FileNotFoundException)) {
|
||||||
|
_vaultFileError = e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void writeToFile(Context context, InputStream inStream) throws IOException {
|
if (_vaultFile != null && !_vaultFile.isEncrypted()) {
|
||||||
AtomicFile file = VaultManager.getAtomicFile(context);
|
|
||||||
|
|
||||||
FileOutputStream outStream = null;
|
|
||||||
try {
|
try {
|
||||||
outStream = file.startWrite();
|
load(_vaultFile, null);
|
||||||
IOUtils.copy(inStream, outStream);
|
} catch (VaultRepositoryException e) {
|
||||||
file.finishWrite(outStream);
|
e.printStackTrace();
|
||||||
} catch (IOException e) {
|
_vaultFile = null;
|
||||||
if (outStream != null) {
|
_vaultFileError = e;
|
||||||
file.failWrite(outStream);
|
|
||||||
}
|
}
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static VaultManager init(Context context, VaultFile file, VaultFileCredentials creds) throws VaultManagerException {
|
/**
|
||||||
if (file.isEncrypted() && creds == null) {
|
* Initializes the vault repository with a new empty vault and the given creds. It can
|
||||||
throw new IllegalArgumentException("The VaultFile is encrypted but the given VaultFileCredentials is null");
|
* 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 init(@Nullable VaultFileCredentials creds) throws VaultRepositoryException {
|
||||||
|
if (isVaultLoaded()) {
|
||||||
|
throw new IllegalStateException("Vault manager is already initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
Vault vault;
|
_vaultFile = null;
|
||||||
|
_vaultFileError = null;
|
||||||
|
_repo = new VaultRepository(_context, new Vault(), creds);
|
||||||
|
save();
|
||||||
|
|
||||||
|
if (getVault().isEncryptionEnabled()) {
|
||||||
|
startNotificationService();
|
||||||
|
}
|
||||||
|
|
||||||
|
return getVault();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the vault repository by decrypting the given vaultFile 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(@NonNull VaultFile vaultFile, @Nullable VaultFileCredentials creds) throws VaultRepositoryException {
|
||||||
|
if (isVaultLoaded()) {
|
||||||
|
throw new IllegalStateException("Vault manager is already initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
_vaultFile = null;
|
||||||
|
_vaultFileError = null;
|
||||||
|
_repo = VaultRepository.fromFile(_context, vaultFile, creds);
|
||||||
|
|
||||||
|
if (getVault().isEncryptionEnabled()) {
|
||||||
|
startNotificationService();
|
||||||
|
}
|
||||||
|
|
||||||
|
return getVault();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public VaultRepository unlock(@NonNull VaultFileCredentials creds) throws VaultRepositoryException {
|
||||||
|
VaultRepository repo = load(getVaultFile(), creds);
|
||||||
|
startNotificationService();
|
||||||
|
return repo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locks the vault and the app.
|
||||||
|
* @param userInitiated whether or not the user initiated the lock in MainActivity.
|
||||||
|
*/
|
||||||
|
public void lock(boolean userInitiated) {
|
||||||
|
_repo = null;
|
||||||
|
|
||||||
|
for (LockListener listener : _lockListeners) {
|
||||||
|
listener.onLocked(userInitiated);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopNotificationService();
|
||||||
|
loadVaultFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enableEncryption(VaultFileCredentials creds) throws VaultRepositoryException {
|
||||||
|
getVault().setCredentials(creds);
|
||||||
|
saveAndBackup();
|
||||||
|
startNotificationService();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disableEncryption() throws VaultRepositoryException {
|
||||||
|
getVault().setCredentials(null);
|
||||||
|
save();
|
||||||
|
|
||||||
|
// remove any keys that are stored in the KeyStore
|
||||||
try {
|
try {
|
||||||
JSONObject obj;
|
KeyStoreHandle handle = new KeyStoreHandle();
|
||||||
if (!file.isEncrypted()) {
|
handle.clear();
|
||||||
obj = file.getContent();
|
} catch (KeyStoreHandleException e) {
|
||||||
} else {
|
// this cleanup operation is not strictly necessary, so we ignore any exceptions here
|
||||||
obj = file.getContent(creds);
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
vault = Vault.fromJson(obj);
|
stopNotificationService();
|
||||||
} catch (VaultException | VaultFileException e) {
|
|
||||||
throw new VaultManagerException(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new VaultManager(context, vault, creds);
|
public void save() throws VaultRepositoryException {
|
||||||
|
getVault().save();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void save(Context context, VaultFile vaultFile) throws VaultManagerException {
|
public void saveAndBackup() throws VaultRepositoryException {
|
||||||
try {
|
save();
|
||||||
byte[] bytes = vaultFile.toBytes();
|
|
||||||
writeToFile(context, new ByteArrayInputStream(bytes));
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new VaultManagerException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void destroy() {
|
if (getVault().isEncryptionEnabled()) {
|
||||||
_backups.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void save(boolean backup) throws VaultManagerException {
|
|
||||||
try {
|
|
||||||
JSONObject obj = _vault.toJson();
|
|
||||||
|
|
||||||
VaultFile file = new VaultFile();
|
|
||||||
if (isEncryptionEnabled()) {
|
|
||||||
file.setContent(obj, _creds);
|
|
||||||
} else {
|
|
||||||
file.setContent(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
save(_context, file);
|
|
||||||
} catch (VaultFileException e) {
|
|
||||||
throw new VaultManagerException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (backup) {
|
|
||||||
if (_prefs.isBackupsEnabled()) {
|
if (_prefs.isBackupsEnabled()) {
|
||||||
try {
|
try {
|
||||||
backup();
|
scheduleBackup();
|
||||||
_prefs.setBackupsError(null);
|
_prefs.setBackupsError(null);
|
||||||
} catch (VaultManagerException e) {
|
} catch (VaultRepositoryException e) {
|
||||||
_prefs.setBackupsError(e);
|
_prefs.setBackupsError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_prefs.isAndroidBackupsEnabled()) {
|
if (_prefs.isAndroidBackupsEnabled()) {
|
||||||
androidBackupDataChanged();
|
scheduleAndroidBackup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void scheduleBackup() throws VaultRepositoryException {
|
||||||
* Exports the vault bt serializing it and writing it to the given OutputStream. If encryption
|
|
||||||
* is enabled, the vault will be encrypted automatically.
|
|
||||||
*/
|
|
||||||
public void export(OutputStream stream) throws VaultManagerException {
|
|
||||||
export(stream, getCredentials());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exports the vault by serializing it and writing it to the given OutputStream. If creds is
|
|
||||||
* not null, it will be used to encrypt the vault first.
|
|
||||||
*/
|
|
||||||
public void export(OutputStream stream, VaultFileCredentials creds) throws VaultManagerException {
|
|
||||||
try {
|
|
||||||
VaultFile vaultFile = new VaultFile();
|
|
||||||
if (creds != null) {
|
|
||||||
vaultFile.setContent(_vault.toJson(), creds);
|
|
||||||
} else {
|
|
||||||
vaultFile.setContent(_vault.toJson());
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] bytes = vaultFile.toBytes();
|
|
||||||
stream.write(bytes);
|
|
||||||
} catch (IOException | VaultFileException e) {
|
|
||||||
throw new VaultManagerException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exports the vault by serializing the list of entries to a newline-separated list of
|
|
||||||
* Google Authenticator URI's and writing it to the given OutputStream.
|
|
||||||
*/
|
|
||||||
public void exportGoogleUris(OutputStream outStream) throws VaultManagerException {
|
|
||||||
try (PrintStream stream = new PrintStream(outStream, false, StandardCharsets.UTF_8.name())) {
|
|
||||||
for (VaultEntry entry : getEntries()) {
|
|
||||||
GoogleAuthInfo info = new GoogleAuthInfo(entry.getInfo(), entry.getName(), entry.getIssuer());
|
|
||||||
stream.println(info.getUri().toString());
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new VaultManagerException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void backup() throws VaultManagerException {
|
|
||||||
try {
|
try {
|
||||||
File dir = new File(_context.getCacheDir(), "backup");
|
File dir = new File(_context.getCacheDir(), "backup");
|
||||||
if (!dir.exists() && !dir.mkdir()) {
|
if (!dir.exists() && !dir.mkdir()) {
|
||||||
|
@ -209,83 +189,154 @@ public class VaultManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
File tempFile = File.createTempFile(VaultBackupManager.FILENAME_PREFIX, ".json", dir);
|
File tempFile = File.createTempFile(VaultBackupManager.FILENAME_PREFIX, ".json", dir);
|
||||||
try (InputStream inStream = getAtomicFile(_context).openRead();
|
getVault().backupTo(tempFile);
|
||||||
OutputStream outStream = new FileOutputStream(tempFile)) {
|
|
||||||
IOUtils.copy(inStream, outStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
_backups.scheduleBackup(tempFile, _prefs.getBackupsLocation(), _prefs.getBackupsVersionCount());
|
_backups.scheduleBackup(tempFile, _prefs.getBackupsLocation(), _prefs.getBackupsVersionCount());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new VaultManagerException(e);
|
throw new VaultRepositoryException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void androidBackupDataChanged() {
|
public void scheduleAndroidBackup() {
|
||||||
_androidBackups.dataChanged();
|
_androidBackups.dataChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addEntry(VaultEntry entry) {
|
public boolean isAutoLockEnabled(int autoLockType) {
|
||||||
_vault.getEntries().add(entry);
|
return _prefs.isAutoLockTypeEnabled(autoLockType)
|
||||||
|
&& isVaultLoaded()
|
||||||
|
&& getVault().isEncryptionEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
public VaultEntry getEntryByUUID(UUID uuid) {
|
public void registerLockListener(LockListener listener) {
|
||||||
return _vault.getEntries().getByUUID(uuid);
|
_lockListeners.add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public VaultEntry removeEntry(VaultEntry entry) {
|
public void unregisterLockListener(LockListener listener) {
|
||||||
return _vault.getEntries().remove(entry);
|
_lockListeners.remove(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void wipeEntries() {
|
/**
|
||||||
_vault.getEntries().wipe();
|
* Sets whether to block automatic lock on minimization. This should only be called
|
||||||
|
* by activities before invoking an intent that shows a DocumentsUI, because that
|
||||||
|
* action leads AppLifecycleObserver to believe that the app has been minimized.
|
||||||
|
*/
|
||||||
|
public void setBlockAutoLock(boolean block) {
|
||||||
|
_blockAutoLock = block;
|
||||||
}
|
}
|
||||||
|
|
||||||
public VaultEntry replaceEntry(VaultEntry entry) {
|
/**
|
||||||
return _vault.getEntries().replace(entry);
|
* Reports whether automatic lock on minimization is currently blocked.
|
||||||
|
*/
|
||||||
|
public boolean isAutoLockBlocked() {
|
||||||
|
return _blockAutoLock;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void swapEntries(VaultEntry entry1, VaultEntry entry2) {
|
public boolean isVaultLoaded() {
|
||||||
_vault.getEntries().swap(entry1, entry2);
|
return _repo != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEntryDuplicate(VaultEntry entry) {
|
public boolean isVaultFileLoaded() {
|
||||||
return _vault.getEntries().has(entry);
|
return _vaultFile != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<VaultEntry> getEntries() {
|
public boolean isVaultInitNeeded() {
|
||||||
return _vault.getEntries().getValues();
|
return !isVaultLoaded() && !isVaultFileLoaded() && getVaultFileError() == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TreeSet<String> getGroups() {
|
@NonNull
|
||||||
TreeSet<String> groups = new TreeSet<>(Collator.getInstance());
|
public VaultRepository getVault() {
|
||||||
for (VaultEntry entry : getEntries()) {
|
if (!isVaultLoaded()) {
|
||||||
String group = entry.getGroup();
|
throw new IllegalStateException("Vault manager is not initialized");
|
||||||
if (group != null) {
|
|
||||||
groups.add(group);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return groups;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public VaultFileCredentials getCredentials() {
|
return _repo;
|
||||||
return _creds;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCredentials(VaultFileCredentials creds) {
|
@NonNull
|
||||||
_creds = creds;
|
public VaultFile getVaultFile() {
|
||||||
|
if (_vaultFile == null) {
|
||||||
|
throw new IllegalStateException("Vault file is not in memory");
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEncryptionEnabled() {
|
return _vaultFile;
|
||||||
return _creds != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enableEncryption(VaultFileCredentials creds) throws VaultManagerException {
|
@Nullable
|
||||||
_creds = creds;
|
public VaultRepositoryException getVaultFileError() {
|
||||||
save(true);
|
return _vaultFileError;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void disableEncryption() throws VaultManagerException {
|
/**
|
||||||
_creds = null;
|
* Starts an external activity, temporarily blocks automatic lock of Aegis and
|
||||||
save(true);
|
* shows an error dialog if the target activity is not found.
|
||||||
|
*/
|
||||||
|
public void startActivityForResult(Activity activity, Intent intent, int requestCode) {
|
||||||
|
setBlockAutoLock(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
activity.startActivityForResult(intent, requestCode, null);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
if (isDocsAction(intent.getAction())) {
|
||||||
|
Dialogs.showErrorDialog(activity, R.string.documentsui_error, e);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts an external activity, temporarily blocks automatic lock of Aegis and
|
||||||
|
* shows an error dialog if the target activity is not found.
|
||||||
|
*/
|
||||||
|
public void startActivity(Fragment fragment, Intent intent) {
|
||||||
|
startActivityForResult(fragment, intent, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts an external activity, temporarily blocks automatic lock of Aegis and
|
||||||
|
* shows an error dialog if the target activity is not found.
|
||||||
|
*/
|
||||||
|
public void startActivityForResult(Fragment fragment, Intent intent, int requestCode) {
|
||||||
|
setBlockAutoLock(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
fragment.startActivityForResult(intent, requestCode, null);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
if (isDocsAction(intent.getAction())) {
|
||||||
|
Dialogs.showErrorDialog(fragment.getContext(), R.string.documentsui_error, e);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startNotificationService() {
|
||||||
|
_context.startService(getNotificationServiceIntent());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopNotificationService() {
|
||||||
|
_context.stopService(getNotificationServiceIntent());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Intent getNotificationServiceIntent() {
|
||||||
|
return new Intent(_context, NotificationService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isDocsAction(@Nullable String action) {
|
||||||
|
return action != null && (action.equals(Intent.ACTION_GET_CONTENT)
|
||||||
|
|| action.equals(Intent.ACTION_CREATE_DOCUMENT)
|
||||||
|
|| action.equals(Intent.ACTION_OPEN_DOCUMENT)
|
||||||
|
|| action.equals(Intent.ACTION_OPEN_DOCUMENT_TREE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface LockListener {
|
||||||
|
/**
|
||||||
|
* Called when the vault lock status changes
|
||||||
|
* @param userInitiated whether or not the user initiated the lock in MainActivity.
|
||||||
|
*/
|
||||||
|
void onLocked(boolean userInitiated);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
package com.beemdevelopment.aegis.vault;
|
|
||||||
|
|
||||||
public class VaultManagerException extends Exception {
|
|
||||||
public VaultManagerException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public VaultManagerException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,236 @@
|
||||||
|
package com.beemdevelopment.aegis.vault;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.util.AtomicFile;
|
||||||
|
|
||||||
|
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
|
||||||
|
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;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.text.Collator;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class VaultRepository {
|
||||||
|
public static final String FILENAME = "aegis.json";
|
||||||
|
public static final String FILENAME_PREFIX_EXPORT = "aegis-export";
|
||||||
|
public static final String FILENAME_PREFIX_EXPORT_PLAIN = "aegis-export-plain";
|
||||||
|
public static final String FILENAME_PREFIX_EXPORT_URI = "aegis-export-uri";
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final Vault _vault;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private VaultFileCredentials _creds;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final Context _context;
|
||||||
|
|
||||||
|
public VaultRepository(@NonNull Context context, @NonNull Vault vault, @Nullable VaultFileCredentials creds) {
|
||||||
|
_context = context;
|
||||||
|
_vault = vault;
|
||||||
|
_creds = creds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AtomicFile getAtomicFile(Context context) {
|
||||||
|
return new AtomicFile(new File(context.getFilesDir(), FILENAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean fileExists(Context context) {
|
||||||
|
File file = getAtomicFile(context).getBaseFile();
|
||||||
|
return file.exists() && file.isFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void deleteFile(Context context) {
|
||||||
|
getAtomicFile(context).delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static VaultFile readVaultFile(Context context) throws VaultRepositoryException {
|
||||||
|
AtomicFile file = getAtomicFile(context);
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte[] fileBytes = file.readFully();
|
||||||
|
return VaultFile.fromBytes(fileBytes);
|
||||||
|
} catch (IOException | VaultFileException e) {
|
||||||
|
throw new VaultRepositoryException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void writeToFile(Context context, InputStream inStream) throws IOException {
|
||||||
|
AtomicFile file = VaultRepository.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 VaultRepository fromFile(Context context, VaultFile file, VaultFileCredentials creds) throws VaultRepositoryException {
|
||||||
|
if (file.isEncrypted() && creds == null) {
|
||||||
|
throw new IllegalArgumentException("The VaultFile is encrypted but the given VaultFileCredentials is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
Vault vault;
|
||||||
|
try {
|
||||||
|
JSONObject obj;
|
||||||
|
if (!file.isEncrypted()) {
|
||||||
|
obj = file.getContent();
|
||||||
|
} else {
|
||||||
|
obj = file.getContent(creds);
|
||||||
|
}
|
||||||
|
|
||||||
|
vault = Vault.fromJson(obj);
|
||||||
|
} catch (VaultException | VaultFileException e) {
|
||||||
|
throw new VaultRepositoryException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new VaultRepository(context, vault, creds);
|
||||||
|
}
|
||||||
|
|
||||||
|
void save() throws VaultRepositoryException {
|
||||||
|
try {
|
||||||
|
JSONObject obj = _vault.toJson();
|
||||||
|
|
||||||
|
VaultFile file = new VaultFile();
|
||||||
|
if (isEncryptionEnabled()) {
|
||||||
|
file.setContent(obj, _creds);
|
||||||
|
} else {
|
||||||
|
file.setContent(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte[] bytes = file.toBytes();
|
||||||
|
writeToFile(_context, new ByteArrayInputStream(bytes));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new VaultRepositoryException(e);
|
||||||
|
}
|
||||||
|
} catch (VaultFileException e) {
|
||||||
|
throw new VaultRepositoryException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports the vault bt serializing it and writing it to the given OutputStream. If encryption
|
||||||
|
* is enabled, the vault will be encrypted automatically.
|
||||||
|
*/
|
||||||
|
public void export(OutputStream stream) throws VaultRepositoryException {
|
||||||
|
export(stream, getCredentials());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports the vault by serializing it and writing it to the given OutputStream. If creds is
|
||||||
|
* not null, it will be used to encrypt the vault first.
|
||||||
|
*/
|
||||||
|
public void export(OutputStream stream, VaultFileCredentials creds) throws VaultRepositoryException {
|
||||||
|
try {
|
||||||
|
VaultFile vaultFile = new VaultFile();
|
||||||
|
if (creds != null) {
|
||||||
|
vaultFile.setContent(_vault.toJson(), creds);
|
||||||
|
} else {
|
||||||
|
vaultFile.setContent(_vault.toJson());
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] bytes = vaultFile.toBytes();
|
||||||
|
stream.write(bytes);
|
||||||
|
} catch (IOException | VaultFileException e) {
|
||||||
|
throw new VaultRepositoryException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports the vault by serializing the list of entries to a newline-separated list of
|
||||||
|
* Google Authenticator URI's and writing it to the given OutputStream.
|
||||||
|
*/
|
||||||
|
public void exportGoogleUris(OutputStream outStream) throws VaultRepositoryException {
|
||||||
|
try (PrintStream stream = new PrintStream(outStream, false, StandardCharsets.UTF_8.name())) {
|
||||||
|
for (VaultEntry entry : getEntries()) {
|
||||||
|
GoogleAuthInfo info = new GoogleAuthInfo(entry.getInfo(), entry.getName(), entry.getIssuer());
|
||||||
|
stream.println(info.getUri().toString());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new VaultRepositoryException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void backupTo(File destFile) throws IOException {
|
||||||
|
try (InputStream inStream = getAtomicFile(_context).openRead();
|
||||||
|
OutputStream outStream = new FileOutputStream(destFile)) {
|
||||||
|
IOUtils.copy(inStream, outStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addEntry(VaultEntry entry) {
|
||||||
|
_vault.getEntries().add(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public VaultEntry getEntryByUUID(UUID uuid) {
|
||||||
|
return _vault.getEntries().getByUUID(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public VaultEntry removeEntry(VaultEntry entry) {
|
||||||
|
return _vault.getEntries().remove(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void wipeEntries() {
|
||||||
|
_vault.getEntries().wipe();
|
||||||
|
}
|
||||||
|
|
||||||
|
public VaultEntry replaceEntry(VaultEntry entry) {
|
||||||
|
return _vault.getEntries().replace(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void swapEntries(VaultEntry entry1, VaultEntry entry2) {
|
||||||
|
_vault.getEntries().swap(entry1, entry2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEntryDuplicate(VaultEntry entry) {
|
||||||
|
return _vault.getEntries().has(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<VaultEntry> getEntries() {
|
||||||
|
return _vault.getEntries().getValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TreeSet<String> getGroups() {
|
||||||
|
TreeSet<String> groups = new TreeSet<>(Collator.getInstance());
|
||||||
|
for (VaultEntry entry : getEntries()) {
|
||||||
|
String group = entry.getGroup();
|
||||||
|
if (group != null) {
|
||||||
|
groups.add(group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VaultFileCredentials getCredentials() {
|
||||||
|
return _creds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCredentials(VaultFileCredentials creds) {
|
||||||
|
_creds = creds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEncryptionEnabled() {
|
||||||
|
return _creds != null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.beemdevelopment.aegis.vault;
|
||||||
|
|
||||||
|
public class VaultRepositoryException extends Exception {
|
||||||
|
public VaultRepositoryException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public VaultRepositoryException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,37 +5,37 @@
|
||||||
android:title="@string/action_settings">
|
android:title="@string/action_settings">
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:fragment="com.beemdevelopment.aegis.ui.fragments.AppearancePreferencesFragment"
|
android:fragment="com.beemdevelopment.aegis.ui.fragments.preferences.AppearancePreferencesFragment"
|
||||||
app:icon="@drawable/ic_brush_black_24dp"
|
app:icon="@drawable/ic_brush_black_24dp"
|
||||||
app:title="@string/pref_section_appearance_title"
|
app:title="@string/pref_section_appearance_title"
|
||||||
app:summary="@string/pref_section_appearance_summary" />
|
app:summary="@string/pref_section_appearance_summary" />
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:fragment="com.beemdevelopment.aegis.ui.fragments.BehaviorPreferencesFragment"
|
android:fragment="com.beemdevelopment.aegis.ui.fragments.preferences.BehaviorPreferencesFragment"
|
||||||
app:icon="@drawable/ic_gesture_tap_24dp"
|
app:icon="@drawable/ic_gesture_tap_24dp"
|
||||||
app:title="@string/pref_section_behavior_title"
|
app:title="@string/pref_section_behavior_title"
|
||||||
app:summary="@string/pref_section_behavior_summary" />
|
app:summary="@string/pref_section_behavior_summary" />
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:fragment="com.beemdevelopment.aegis.ui.fragments.IconPacksManagerFragment"
|
android:fragment="com.beemdevelopment.aegis.ui.fragments.preferences.IconPacksManagerFragment"
|
||||||
android:title="@string/pref_section_icon_packs"
|
android:title="@string/pref_section_icon_packs"
|
||||||
android:summary="@string/pref_section_icon_packs_summary"
|
android:summary="@string/pref_section_icon_packs_summary"
|
||||||
app:icon="@drawable/ic_package_variant_black_24dp"/>
|
app:icon="@drawable/ic_package_variant_black_24dp"/>
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:fragment="com.beemdevelopment.aegis.ui.fragments.SecurityPreferencesFragment"
|
android:fragment="com.beemdevelopment.aegis.ui.fragments.preferences.SecurityPreferencesFragment"
|
||||||
app:icon="@drawable/ic_vpn_key_black_24dp"
|
app:icon="@drawable/ic_vpn_key_black_24dp"
|
||||||
app:title="@string/pref_section_security_title"
|
app:title="@string/pref_section_security_title"
|
||||||
app:summary="@string/pref_section_security_summary" />
|
app:summary="@string/pref_section_security_summary" />
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:fragment="com.beemdevelopment.aegis.ui.fragments.BackupsPreferencesFragment"
|
android:fragment="com.beemdevelopment.aegis.ui.fragments.preferences.BackupsPreferencesFragment"
|
||||||
app:icon="@drawable/ic_cloud_upload_outline_black_24dp"
|
app:icon="@drawable/ic_cloud_upload_outline_black_24dp"
|
||||||
app:title="@string/pref_section_backups_title"
|
app:title="@string/pref_section_backups_title"
|
||||||
app:summary="@string/pref_section_backups_summary" />
|
app:summary="@string/pref_section_backups_summary" />
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:fragment="com.beemdevelopment.aegis.ui.fragments.ImportExportPreferencesFragment"
|
android:fragment="com.beemdevelopment.aegis.ui.fragments.preferences.ImportExportPreferencesFragment"
|
||||||
app:icon="@drawable/ic_tools_black_24dp"
|
app:icon="@drawable/ic_tools_black_24dp"
|
||||||
app:title="@string/pref_section_import_export_title"
|
app:title="@string/pref_section_import_export_title"
|
||||||
app:summary="@string/pref_section_import_export_summary" />
|
app:summary="@string/pref_section_import_export_summary" />
|
||||||
|
|
|
@ -7,6 +7,7 @@ buildscript {
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.0.4'
|
classpath 'com.android.tools.build:gradle:7.0.4'
|
||||||
|
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.38.1'
|
||||||
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.18'
|
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.18'
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
|
Loading…
Add table
Reference in a new issue