Use Dagger Hilt for dependency injection

This gets rid of our own janky dependency injection through the AegisApplication class
This commit is contained in:
Alexander Bakker 2022-02-06 19:00:01 +01:00
parent 927f5f2bd5
commit 71f2b54deb
42 changed files with 1157 additions and 977 deletions

View file

@ -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}"

View file

@ -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;
} }

View file

@ -0,0 +1,7 @@
package com.beemdevelopment.aegis;
import dagger.hilt.android.testing.CustomTestApplication;
@CustomTestApplication(AegisApplicationBase.class)
public interface AegisTestApplication {
}

View file

@ -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();

View file

@ -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));
} }

View file

@ -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));

View file

@ -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());

View file

@ -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() {

View file

@ -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);
} }
} }

View file

@ -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);
}
} }

View file

@ -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();
}
}

View file

@ -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();
} }
} }

View 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);
}
}

View file

@ -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() {

View file

@ -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));
}
} }
} }

View file

@ -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;

View file

@ -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();
} }

View file

@ -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));

View file

@ -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();

View file

@ -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();

View file

@ -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;
} }
} }

View file

@ -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;
} }

View file

@ -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));

View file

@ -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));

View file

@ -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;

View file

@ -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);

View file

@ -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();
} }
} }

View file

@ -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);
} }
} }

View file

@ -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);

View file

@ -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() {

View file

@ -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 {

View file

@ -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;

View file

@ -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;

View file

@ -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();
} }

View file

@ -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));

View file

@ -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

View file

@ -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);
} }
} }

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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" />

View file

@ -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