2019-12-25 19:21:34 +01:00
|
|
|
package com.beemdevelopment.aegis.vault;
|
2017-08-06 16:03:36 +02:00
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
import android.app.Activity;
|
2021-01-03 18:19:16 +01:00
|
|
|
import android.app.backup.BackupManager;
|
2022-02-06 19:00:01 +01:00
|
|
|
import android.content.ActivityNotFoundException;
|
2017-08-06 16:03:36 +02:00
|
|
|
import android.content.Context;
|
2022-02-06 19:00:01 +01:00
|
|
|
import android.content.Intent;
|
2017-08-06 16:03:36 +02:00
|
|
|
|
2023-12-21 22:56:50 +01:00
|
|
|
import androidx.activity.result.ActivityResultLauncher;
|
2022-02-06 19:00:01 +01:00
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
import androidx.annotation.Nullable;
|
|
|
|
import androidx.fragment.app.Fragment;
|
2020-02-02 13:23:45 +01:00
|
|
|
|
2020-01-04 22:06:59 +01:00
|
|
|
import com.beemdevelopment.aegis.Preferences;
|
2022-02-06 19:00:01 +01:00
|
|
|
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;
|
2019-04-04 14:07:36 +02:00
|
|
|
|
2017-12-10 19:19:48 +01:00
|
|
|
import java.io.File;
|
2022-06-05 18:26:46 +02:00
|
|
|
import java.io.FileOutputStream;
|
2017-12-10 19:19:48 +01:00
|
|
|
import java.io.IOException;
|
2022-06-05 18:26:46 +02:00
|
|
|
import java.io.OutputStream;
|
2022-02-06 19:00:01 +01:00
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.List;
|
2017-08-06 16:03:36 +02:00
|
|
|
|
2019-12-25 19:21:34 +01:00
|
|
|
public class VaultManager {
|
2022-02-06 19:00:01 +01:00
|
|
|
private final Context _context;
|
|
|
|
private final Preferences _prefs;
|
|
|
|
|
|
|
|
private VaultRepository _repo;
|
2017-12-04 22:08:50 +01:00
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
private final VaultBackupManager _backups;
|
|
|
|
private final BackupManager _androidBackups;
|
2018-10-06 22:23:38 +02:00
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
private final List<LockListener> _lockListeners;
|
|
|
|
private boolean _blockAutoLock;
|
2017-08-06 16:03:36 +02:00
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
public VaultManager(@NonNull Context context) {
|
2017-08-06 16:03:36 +02:00
|
|
|
_context = context;
|
2022-02-06 19:00:01 +01:00
|
|
|
_prefs = new Preferences(_context);
|
|
|
|
_backups = new VaultBackupManager(_context);
|
2021-01-03 18:19:16 +01:00
|
|
|
_androidBackups = new BackupManager(context);
|
2022-02-06 19:00:01 +01:00
|
|
|
_lockListeners = new ArrayList<>();
|
2020-08-16 22:45:02 +02:00
|
|
|
}
|
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
/**
|
|
|
|
* 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 {
|
2022-02-06 19:00:01 +01:00
|
|
|
if (isVaultLoaded()) {
|
|
|
|
throw new IllegalStateException("Vault manager is already initialized");
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|
|
|
|
|
2024-03-10 19:50:17 +01:00
|
|
|
VaultRepository repo = new VaultRepository(_context, new Vault(), creds);
|
|
|
|
repo.save();
|
|
|
|
_repo = repo;
|
2021-01-03 18:19:16 +01:00
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
if (getVault().isEncryptionEnabled()) {
|
|
|
|
startNotificationService();
|
2021-01-03 18:19:16 +01:00
|
|
|
}
|
2022-02-06 19:00:01 +01:00
|
|
|
|
|
|
|
return getVault();
|
2021-01-03 18:19:16 +01:00
|
|
|
}
|
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
/**
|
|
|
|
* 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 {
|
2022-02-06 19:00:01 +01:00
|
|
|
if (isVaultLoaded()) {
|
|
|
|
throw new IllegalStateException("Vault manager is already initialized");
|
2020-05-02 18:51:12 +02:00
|
|
|
}
|
2017-08-06 16:03:36 +02:00
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
_repo = VaultRepository.fromFile(_context, vaultFile, creds);
|
2020-05-02 18:51:12 +02:00
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
if (getVault().isEncryptionEnabled()) {
|
|
|
|
startNotificationService();
|
2018-03-19 18:00:53 +01:00
|
|
|
}
|
2020-05-02 18:51:12 +02:00
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
return getVault();
|
2018-03-19 18:00:53 +01:00
|
|
|
}
|
2017-12-10 19:19:48 +01:00
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
@NonNull
|
2023-03-01 18:01:25 +01:00
|
|
|
public VaultRepository loadFrom(@NonNull VaultFile vaultFile) throws VaultRepositoryException {
|
|
|
|
return loadFrom(vaultFile, null);
|
2022-02-06 19:00:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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);
|
2017-12-10 19:19:48 +01:00
|
|
|
}
|
2022-02-06 19:00:01 +01:00
|
|
|
|
|
|
|
stopNotificationService();
|
2017-12-10 19:19:48 +01:00
|
|
|
}
|
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
public void enableEncryption(VaultFileCredentials creds) throws VaultRepositoryException {
|
|
|
|
getVault().setCredentials(creds);
|
|
|
|
saveAndBackup();
|
|
|
|
startNotificationService();
|
2020-11-20 16:28:08 +01:00
|
|
|
}
|
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
public void disableEncryption() throws VaultRepositoryException {
|
|
|
|
getVault().setCredentials(null);
|
|
|
|
save();
|
|
|
|
|
|
|
|
// remove any keys that are stored in the KeyStore
|
2018-03-19 18:00:53 +01:00
|
|
|
try {
|
2022-02-06 19:00:01 +01:00
|
|
|
KeyStoreHandle handle = new KeyStoreHandle();
|
|
|
|
handle.clear();
|
|
|
|
} catch (KeyStoreHandleException e) {
|
|
|
|
// this cleanup operation is not strictly necessary, so we ignore any exceptions here
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
2020-05-02 18:51:12 +02:00
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
stopNotificationService();
|
|
|
|
}
|
2020-05-02 18:51:12 +02:00
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
public void save() throws VaultRepositoryException {
|
|
|
|
getVault().save();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void saveAndBackup() throws VaultRepositoryException {
|
|
|
|
save();
|
2020-01-04 22:06:59 +01:00
|
|
|
|
2022-04-10 16:47:44 +02:00
|
|
|
boolean backedUp = false;
|
2022-02-06 19:00:01 +01:00
|
|
|
if (getVault().isEncryptionEnabled()) {
|
2021-01-03 18:19:16 +01:00
|
|
|
if (_prefs.isBackupsEnabled()) {
|
2022-04-10 16:47:44 +02:00
|
|
|
backedUp = true;
|
2021-01-03 18:19:16 +01:00
|
|
|
try {
|
2022-02-06 19:00:01 +01:00
|
|
|
scheduleBackup();
|
|
|
|
} catch (VaultRepositoryException e) {
|
2022-10-05 18:21:50 +02:00
|
|
|
_prefs.setBuiltInBackupResult(new Preferences.BackupResult(e));
|
2021-01-03 18:19:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_prefs.isAndroidBackupsEnabled()) {
|
2022-04-10 16:47:44 +02:00
|
|
|
backedUp = true;
|
2022-02-06 19:00:01 +01:00
|
|
|
scheduleAndroidBackup();
|
2017-12-10 19:19:48 +01:00
|
|
|
}
|
2020-10-25 21:42:26 +01:00
|
|
|
}
|
2022-04-10 16:47:44 +02:00
|
|
|
|
|
|
|
if (!backedUp) {
|
|
|
|
_prefs.setIsBackupReminderNeeded(true);
|
|
|
|
}
|
2020-10-25 21:42:26 +01:00
|
|
|
}
|
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
public void scheduleBackup() throws VaultRepositoryException {
|
2022-04-10 16:47:44 +02:00
|
|
|
_prefs.setIsBackupReminderNeeded(false);
|
|
|
|
|
2020-11-20 16:28:08 +01:00
|
|
|
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);
|
2022-06-05 18:26:46 +02:00
|
|
|
try (OutputStream outStream = new FileOutputStream(tempFile)) {
|
|
|
|
_repo.export(outStream);
|
|
|
|
}
|
|
|
|
|
2020-11-20 16:28:08 +01:00
|
|
|
_backups.scheduleBackup(tempFile, _prefs.getBackupsLocation(), _prefs.getBackupsVersionCount());
|
|
|
|
} catch (IOException e) {
|
2022-02-06 19:00:01 +01:00
|
|
|
throw new VaultRepositoryException(e);
|
2020-11-20 16:28:08 +01:00
|
|
|
}
|
2020-01-04 22:06:59 +01:00
|
|
|
}
|
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
public void scheduleAndroidBackup() {
|
2022-04-10 16:47:44 +02:00
|
|
|
_prefs.setIsBackupReminderNeeded(false);
|
2021-01-03 18:19:16 +01:00
|
|
|
_androidBackups.dataChanged();
|
|
|
|
}
|
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
public boolean isAutoLockEnabled(int autoLockType) {
|
|
|
|
return _prefs.isAutoLockTypeEnabled(autoLockType)
|
|
|
|
&& isVaultLoaded()
|
|
|
|
&& getVault().isEncryptionEnabled();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void registerLockListener(LockListener listener) {
|
|
|
|
_lockListeners.add(listener);
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
public void unregisterLockListener(LockListener listener) {
|
|
|
|
_lockListeners.remove(listener);
|
2020-05-02 18:51:33 +02:00
|
|
|
}
|
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
/**
|
|
|
|
* 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;
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
/**
|
|
|
|
* Reports whether automatic lock on minimization is currently blocked.
|
|
|
|
*/
|
|
|
|
public boolean isAutoLockBlocked() {
|
|
|
|
return _blockAutoLock;
|
2020-08-16 03:33:53 +02:00
|
|
|
}
|
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
public boolean isVaultLoaded() {
|
|
|
|
return _repo != null;
|
2017-12-27 22:04:22 +01:00
|
|
|
}
|
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
public boolean isVaultInitNeeded() {
|
2023-03-01 18:01:25 +01:00
|
|
|
return !isVaultLoaded() && !VaultRepository.fileExists(_context);
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
@NonNull
|
|
|
|
public VaultRepository getVault() {
|
|
|
|
if (!isVaultLoaded()) {
|
|
|
|
throw new IllegalStateException("Vault manager is not initialized");
|
|
|
|
}
|
|
|
|
|
|
|
|
return _repo;
|
2018-09-22 14:12:42 +02:00
|
|
|
}
|
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
/**
|
|
|
|
* Starts an external activity, temporarily blocks automatic lock of Aegis and
|
|
|
|
* shows an error dialog if the target activity is not found.
|
|
|
|
*/
|
2023-12-21 22:56:50 +01:00
|
|
|
public void fireIntentLauncher(Activity activity, Intent intent, ActivityResultLauncher<Intent> resultLauncher) {
|
2022-02-06 19:00:01 +01:00
|
|
|
setBlockAutoLock(true);
|
|
|
|
|
|
|
|
try {
|
2023-12-21 22:56:50 +01:00
|
|
|
resultLauncher.launch(intent);
|
2022-02-06 19:00:01 +01:00
|
|
|
} 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.
|
|
|
|
*/
|
2023-12-21 22:56:50 +01:00
|
|
|
public void fireIntentLauncher(Fragment fragment, Intent intent, ActivityResultLauncher<Intent> resultLauncher) {
|
2022-02-06 19:00:01 +01:00
|
|
|
setBlockAutoLock(true);
|
|
|
|
|
|
|
|
try {
|
2023-12-21 22:56:50 +01:00
|
|
|
resultLauncher.launch(intent);
|
2022-02-06 19:00:01 +01:00
|
|
|
} catch (ActivityNotFoundException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
|
|
|
|
if (isDocsAction(intent.getAction())) {
|
2022-04-10 18:44:25 +02:00
|
|
|
Dialogs.showErrorDialog(fragment.requireContext(), R.string.documentsui_error, e);
|
2022-02-06 19:00:01 +01:00
|
|
|
} else {
|
|
|
|
throw e;
|
2018-12-11 11:44:36 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
private void startNotificationService() {
|
2022-12-03 14:12:31 +01:00
|
|
|
// NOTE: Disabled for now. See issue: #1047
|
|
|
|
/*if (PermissionHelper.granted(_context, Manifest.permission.POST_NOTIFICATIONS)) {
|
2022-09-14 19:29:36 +02:00
|
|
|
_context.startService(getNotificationServiceIntent());
|
2022-12-03 14:12:31 +01:00
|
|
|
}*/
|
2018-02-09 17:31:07 +01:00
|
|
|
}
|
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
private void stopNotificationService() {
|
2022-12-03 14:12:31 +01:00
|
|
|
// NOTE: Disabled for now. See issue: #1047
|
|
|
|
//_context.stopService(getNotificationServiceIntent());
|
2017-08-06 18:15:47 +02:00
|
|
|
}
|
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
private Intent getNotificationServiceIntent() {
|
|
|
|
return new Intent(_context, NotificationService.class);
|
2018-10-06 22:23:38 +02:00
|
|
|
}
|
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
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));
|
2018-05-14 16:53:27 +02:00
|
|
|
}
|
|
|
|
|
2022-02-06 19:00:01 +01:00
|
|
|
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);
|
2018-05-14 16:53:27 +02:00
|
|
|
}
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|