Aegis/app/src/main/java/com/beemdevelopment/aegis/vault/VaultManager.java

301 lines
9.5 KiB
Java
Raw Normal View History

package com.beemdevelopment.aegis.vault;
import android.app.Activity;
import android.app.backup.BackupManager;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.beemdevelopment.aegis.Preferences;
import com.beemdevelopment.aegis.R;
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 java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
public class VaultManager {
private final Context _context;
private final Preferences _prefs;
private VaultRepository _repo;
2017-12-04 22:08:50 +01:00
private final VaultBackupManager _backups;
private final BackupManager _androidBackups;
private final List<LockListener> _lockListeners;
private boolean _blockAutoLock;
public VaultManager(@NonNull Context context) {
_context = context;
_prefs = new Preferences(_context);
_backups = new VaultBackupManager(_context);
_androidBackups = new BackupManager(context);
_lockListeners = new ArrayList<>();
}
/**
* Initializes the vault repository with a new empty vault and the given creds. It can
* only be called if isVaultLoaded() returns false.
*/
@NonNull
2022-06-04 18:34:52 +02:00
public VaultRepository initNew(@Nullable VaultFileCredentials creds) throws VaultRepositoryException {
if (isVaultLoaded()) {
throw new IllegalStateException("Vault manager is already initialized");
}
VaultRepository repo = new VaultRepository(_context, new Vault(), creds);
repo.save();
_repo = repo;
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.
*/
@NonNull
2022-06-04 18:34:52 +02:00
public VaultRepository loadFrom(@NonNull VaultFile vaultFile, @Nullable VaultFileCredentials creds) throws VaultRepositoryException {
if (isVaultLoaded()) {
throw new IllegalStateException("Vault manager is already initialized");
}
_repo = VaultRepository.fromFile(_context, vaultFile, creds);
if (getVault().isEncryptionEnabled()) {
startNotificationService();
}
return getVault();
}
@NonNull
public VaultRepository loadFrom(@NonNull VaultFile vaultFile) throws VaultRepositoryException {
return loadFrom(vaultFile, null);
}
/**
* 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();
}
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 {
KeyStoreHandle handle = new KeyStoreHandle();
handle.clear();
} catch (KeyStoreHandleException e) {
// this cleanup operation is not strictly necessary, so we ignore any exceptions here
e.printStackTrace();
}
stopNotificationService();
}
public void save() throws VaultRepositoryException {
getVault().save();
}
public void saveAndBackup() throws VaultRepositoryException {
save();
boolean backedUp = false;
if (getVault().isEncryptionEnabled()) {
if (_prefs.isBackupsEnabled()) {
backedUp = true;
try {
scheduleBackup();
} catch (VaultRepositoryException e) {
_prefs.setBuiltInBackupResult(new Preferences.BackupResult(e));
}
}
if (_prefs.isAndroidBackupsEnabled()) {
backedUp = true;
scheduleAndroidBackup();
}
}
if (!backedUp) {
_prefs.setIsBackupReminderNeeded(true);
}
}
public void scheduleBackup() throws VaultRepositoryException {
_prefs.setIsBackupReminderNeeded(false);
try {
File dir = new File(_context.getCacheDir(), "backup");
if (!dir.exists() && !dir.mkdir()) {
throw new IOException(String.format("Unable to create directory %s", dir));
}
File tempFile = File.createTempFile(VaultBackupManager.FILENAME_PREFIX, ".json", dir);
try (OutputStream outStream = new FileOutputStream(tempFile)) {
_repo.export(outStream);
}
_backups.scheduleBackup(tempFile, _prefs.getBackupsLocation(), _prefs.getBackupsVersionCount());
} catch (IOException e) {
throw new VaultRepositoryException(e);
}
}
public void scheduleAndroidBackup() {
_prefs.setIsBackupReminderNeeded(false);
_androidBackups.dataChanged();
}
public boolean isAutoLockEnabled(int autoLockType) {
return _prefs.isAutoLockTypeEnabled(autoLockType)
&& isVaultLoaded()
&& getVault().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;
}
/**
* Reports whether automatic lock on minimization is currently blocked.
*/
public boolean isAutoLockBlocked() {
return _blockAutoLock;
}
public boolean isVaultLoaded() {
return _repo != null;
}
public boolean isVaultInitNeeded() {
return !isVaultLoaded() && !VaultRepository.fileExists(_context);
}
@NonNull
public VaultRepository getVault() {
if (!isVaultLoaded()) {
throw new IllegalStateException("Vault manager is not initialized");
}
return _repo;
}
/**
* Starts an external activity, temporarily blocks automatic lock of Aegis and
* shows an error dialog if the target activity is not found.
*/
public void fireIntentLauncher(Activity activity, Intent intent, ActivityResultLauncher<Intent> resultLauncher) {
setBlockAutoLock(true);
try {
resultLauncher.launch(intent);
} 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 fireIntentLauncher(Fragment fragment, Intent intent, ActivityResultLauncher<Intent> resultLauncher) {
setBlockAutoLock(true);
try {
resultLauncher.launch(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
if (isDocsAction(intent.getAction())) {
Dialogs.showErrorDialog(fragment.requireContext(), R.string.documentsui_error, e);
} else {
throw e;
}
}
}
private void startNotificationService() {
// NOTE: Disabled for now. See issue: #1047
/*if (PermissionHelper.granted(_context, Manifest.permission.POST_NOTIFICATIONS)) {
_context.startService(getNotificationServiceIntent());
}*/
}
private void stopNotificationService() {
// NOTE: Disabled for now. See issue: #1047
//_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);
}
}