diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c28e6f26..dad8575d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -34,7 +34,7 @@ android:name=".activity.security.SetupKeyActivity" android:exported="false" android:screenOrientation="locked" - android:theme="@style/AppTheme.NoActionBar" /> + android:theme="@style/AppTheme" /> > menuItemsMap = new HashMap<>(); + private final SparseArray menuActions = new SparseArray<>(); private final Map notesIdsMap = new HashMap<>(); private final Handler searchHandler = new Handler(Looper.getMainLooper()); private ActivityResultLauncher noteEditorLauncher; + private LockAction lockAction; + private GenerateNewKeyAction generateNewKeyAction; @Override protected void onCreate(Bundle savedInstanceState) { @@ -70,18 +74,16 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_note_list); applyInsets(findViewById(R.id.main)); - getOnBackPressedDispatcher() - .addCallback(this, new OnBackPressedCallback(true) { - @Override - public void handleOnBackPressed() { - LockOnClick lock = new LockOnClick(); - lock.accept(NotesListActivity.this); - } - }); + CryptoManager cryptoManager = CryptoManagerProvider.getInstance(getApplicationContext()); + + lockAction = new LockAction(this, cryptoManager); + generateNewKeyAction = new GenerateNewKeyAction(this, cryptoManager); noteEditorLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), getOpenNoteResultCallback()); + getOnBackPressedDispatcher().addCallback(this, getOnBackPressedCallback()); + ListView notesView = findViewById(R.id.notesListView); FloatingActionButton newNoteButton = findViewById(R.id.addNoteButton); @@ -100,20 +102,15 @@ public void handleOnBackPressed() { public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); - Intent importActivityIntent = new Intent(this, ImportActivity.class); - Intent exportActivityIntent = new Intent(this, ExportActivity.class); - getMenuInflater().inflate(R.menu.menu_notes_list, menu); - menuItemsMap.put(R.id.lockAppButton, new LockOnClick()); - menuItemsMap.put(R.id.changePasswordMenuItem, new ChangePasswordOnClick()); - menuItemsMap.put(R.id.generateNewKeyMenuItem, new GenerateNewKeyOnClick()); - - menuItemsMap.put(R.id.exportMenuItem, action -> - startActivity(exportActivityIntent)); - - menuItemsMap.put(R.id.importMenuItem, action -> - startActivity(importActivityIntent)); + menuActions.put(R.id.lockAppButton, lockAction::lock); + menuActions.put(R.id.changePasswordMenuItem, this::startChangePasswordActivity); + menuActions.put(R.id.generateNewKeyMenuItem, generateNewKeyAction::startActivity); + menuActions.put(R.id.exportMenuItem, + () -> startActivity(new Intent(this, ExportActivity.class))); + menuActions.put(R.id.importMenuItem, + () -> startActivity(new Intent(this, ImportActivity.class))); SearchView searchView = (SearchView) menu.findItem(R.id.searchNotesButton).getActionView(); requireNonNull(searchView, "SearchView is null"); @@ -131,7 +128,7 @@ public boolean onOptionsItemSelected(MenuItem item) { int itemId = item.getItemId(); if (itemId != R.id.searchNotesButton) { - requireNonNull(menuItemsMap.get(itemId)).accept(this); + requireNonNull(menuActions.get(itemId)).run(); } return true; @@ -250,4 +247,19 @@ private void openNote(String noteId) { noteEditorLauncher.launch(intent); } + + private void startChangePasswordActivity() { + Intent intent = new Intent(getApplicationContext(), AuthActivity.class) + .putExtra(AuthActivity.EXTRA_MODE, AuthActivity.Mode.CHANGE_PASSWORD.toString()); + startActivity(intent); + } + + private OnBackPressedCallback getOnBackPressedCallback() { + return new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + lockAction.lock(); + } + }; + } } diff --git a/app/src/main/java/app/notesr/activity/security/AuthActivityExtension.java b/app/src/main/java/app/notesr/activity/security/AuthActivityExtension.java index c1ddc9e3..913cc4e1 100644 --- a/app/src/main/java/app/notesr/activity/security/AuthActivityExtension.java +++ b/app/src/main/java/app/notesr/activity/security/AuthActivityExtension.java @@ -71,7 +71,7 @@ public void authorize() { } public void createPassword() { - var password = proceedPasswordSetting(); + char[] password = proceedPasswordSetting(); if (password != null) { var context = activity.getApplicationContext(); @@ -80,7 +80,7 @@ public void createPassword() { try { var passwordBytes = charsToBytes(password, StandardCharsets.UTF_8); - SecretCache.put(SetupKeyActivity.PASSWORD, passwordBytes); + SecretCache.put(SetupKeyActivity.CACHE_KEY_PASSWORD, passwordBytes); } catch (CharacterCodingException e) { throw new RuntimeException(e); } @@ -90,11 +90,11 @@ public void createPassword() { } public void recoverKey() { - var password = proceedPasswordSetting(); + char[] password = proceedPasswordSetting(); if (password != null) { try { - var hexKey = bytesToChars(SecretCache.take(HEX_KEY), + char[] hexKey = bytesToChars(SecretCache.take(HEX_KEY), StandardCharsets.UTF_8); if (hexKey == null) { @@ -118,19 +118,19 @@ public void recoverKey() { } public void changePassword() { - var password = proceedPasswordSetting(); + char[] password = proceedPasswordSetting(); if (password != null) { try { - var context = activity.getApplicationContext(); - var secrets = cryptoManager.getSecrets(); + Context context = activity.getApplicationContext(); + CryptoSecrets secrets = cryptoManager.getSecrets(); secrets.setPassword(password); cryptoManager.setSecrets(context, secrets); secrets.destroy(); showToastMessage(R.string.updated); - activity.startActivity(new Intent(context, NotesListActivity.class)); + activity.startActivity(new Intent(context, NotesListActivity.class)); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/app/src/main/java/app/notesr/activity/security/ChangePasswordOnClick.java b/app/src/main/java/app/notesr/activity/security/ChangePasswordOnClick.java deleted file mode 100644 index 4c1652e9..00000000 --- a/app/src/main/java/app/notesr/activity/security/ChangePasswordOnClick.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2026 zHd4 - * SPDX-License-Identifier: MIT - */ - -package app.notesr.activity.security; - -import android.content.Intent; - -import app.notesr.activity.ActivityBase; - -import java.util.function.Consumer; - -public final class ChangePasswordOnClick implements Consumer { - @Override - public void accept(ActivityBase activity) { - Intent authActivityIntent = new Intent(activity.getApplicationContext(), AuthActivity.class) - .putExtra(AuthActivity.EXTRA_MODE, AuthActivity.Mode.CHANGE_PASSWORD.toString()); - - activity.startActivity(authActivityIntent); - activity.finish(); - } -} diff --git a/app/src/main/java/app/notesr/activity/security/GenerateNewKeyAction.java b/app/src/main/java/app/notesr/activity/security/GenerateNewKeyAction.java new file mode 100644 index 00000000..0af85e83 --- /dev/null +++ b/app/src/main/java/app/notesr/activity/security/GenerateNewKeyAction.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2026 zHd4 + * SPDX-License-Identifier: MIT + */ + +package app.notesr.activity.security; + +import static app.notesr.core.util.CharUtils.charsToBytes; + +import android.content.Context; +import android.content.Intent; + +import app.notesr.activity.ActivityBase; +import app.notesr.core.security.SecretCache; +import app.notesr.core.security.crypto.CryptoManager; +import app.notesr.core.security.dto.CryptoSecrets; +import lombok.RequiredArgsConstructor; + +import java.nio.charset.CharacterCodingException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +@RequiredArgsConstructor +public final class GenerateNewKeyAction { + + private final ActivityBase activity; + private final CryptoManager cryptoManager; + + public void startActivity() { + CryptoSecrets secrets = cryptoManager.getSecrets(); + char[] passwordChars = Arrays.copyOf(secrets.getPassword(), secrets.getPassword().length); + + try { + byte[] passwordBytes = charsToBytes(passwordChars, StandardCharsets.UTF_8); + SecretCache.put(SetupKeyActivity.CACHE_KEY_PASSWORD, passwordBytes); + } catch (CharacterCodingException e) { + throw new RuntimeException(e); + } + + secrets.destroy(); + + Context context = activity.getApplicationContext(); + var intent = new Intent(context, SetupKeyActivity.class) + .putExtra(SetupKeyActivity.EXTRA_MODE, KeySetupMode.REGENERATION.toString()); + activity.startActivity(intent); + } +} diff --git a/app/src/main/java/app/notesr/activity/security/GenerateNewKeyOnClick.java b/app/src/main/java/app/notesr/activity/security/GenerateNewKeyOnClick.java deleted file mode 100644 index e89f33c2..00000000 --- a/app/src/main/java/app/notesr/activity/security/GenerateNewKeyOnClick.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2026 zHd4 - * SPDX-License-Identifier: MIT - */ - -package app.notesr.activity.security; - -import static app.notesr.core.util.CharUtils.charsToBytes; - -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; - -import app.notesr.activity.ActivityBase; -import app.notesr.R; -import app.notesr.activity.DialogFactory; -import app.notesr.core.security.SecretCache; -import app.notesr.core.security.crypto.CryptoManager; -import app.notesr.core.security.crypto.CryptoManagerProvider; - -import java.nio.charset.CharacterCodingException; -import java.nio.charset.StandardCharsets; -import java.util.function.Consumer; - -public final class GenerateNewKeyOnClick implements Consumer { - @Override - public void accept(ActivityBase activity) { - DialogInterface.OnClickListener buttonHandler = regenerateKeyDialogOnClick(activity); - new DialogFactory(activity) - .getThemedAlertDialogBuilder(R.layout.dialog_re_encryption_warning) - .setTitle(R.string.warning) - .setPositiveButton(R.string.yes, buttonHandler) - .setNegativeButton(R.string.no, buttonHandler) - .create() - .show(); - } - - private DialogInterface.OnClickListener regenerateKeyDialogOnClick(ActivityBase activity) { - return (dialog, result) -> { - if (result == DialogInterface.BUTTON_POSITIVE) { - Context context = activity.getApplicationContext(); - Intent intent = new Intent(context, SetupKeyActivity.class); - - CryptoManager cryptoManager = CryptoManagerProvider.getInstance(context); - char[] password = cryptoManager.getSecrets().getPassword(); - - intent.putExtra(SetupKeyActivity.EXTRA_MODE, KeySetupMode.REGENERATION.toString()); - - try { - byte[] passwordBytes = charsToBytes(password, StandardCharsets.UTF_8); - SecretCache.put(SetupKeyActivity.PASSWORD, passwordBytes); - } catch (CharacterCodingException e) { - throw new RuntimeException(e); - } - - activity.startActivity(intent); - } - }; - } -} diff --git a/app/src/main/java/app/notesr/activity/security/ImportKeyActivity.java b/app/src/main/java/app/notesr/activity/security/ImportKeyActivity.java index 3a536f40..3d231dd6 100644 --- a/app/src/main/java/app/notesr/activity/security/ImportKeyActivity.java +++ b/app/src/main/java/app/notesr/activity/security/ImportKeyActivity.java @@ -26,6 +26,7 @@ import java.nio.charset.CharacterCodingException; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import app.notesr.R; import app.notesr.activity.ActivityBase; @@ -37,12 +38,14 @@ public final class ImportKeyActivity extends ActivityBase { - public static final String PASSWORD = "password"; + public static final String CACHE_KEY_PASSWORD = "password"; public static final String EXTRA_MODE = "mode"; private static final String TAG = ImportKeyActivity.class.getCanonicalName(); private KeySetupMode mode; private EditText keyField; + private char[] hexKey; + private char[] password; @Override protected void onCreate(Bundle savedInstanceState) { @@ -56,6 +59,7 @@ protected void onCreate(Bundle savedInstanceState) { actionBar.setTitle(getResources().getString(R.string.import_key)); mode = KeySetupMode.valueOf(requireNonNull(getIntent().getStringExtra(EXTRA_MODE))); + password = getPasswordFromCache(); keyField = findViewById(R.id.importKeyField); keyField.setImeOptions(IME_FLAG_NO_PERSONALIZED_LEARNING); @@ -65,50 +69,72 @@ protected void onCreate(Bundle savedInstanceState) { } @Override - public boolean onSupportNavigateUp() { - super.onBackPressed(); - return true; + protected boolean requiresSession() { + return false; } @Override - protected boolean requiresSession() { - return false; + public void finish() { + wipeSecrets(); + super.finish(); } private View.OnClickListener importKeyButtonOnClick() { return view -> { Editable hexKeyEditable = keyField.getText(); - char[] hexKey = new char[hexKeyEditable.length()]; + hexKey = new char[hexKeyEditable.length()]; hexKeyEditable.getChars(0, hexKeyEditable.length(), hexKey, 0); - hexKeyEditable.replace(0, hexKeyEditable.length(), ""); - keyField.setText(""); if (hexKey.length > 0) { - try { - Context context = getApplicationContext(); + Context context = getApplicationContext(); + CryptoManager cryptoManager = CryptoManagerProvider.getInstance(context); - char[] password = bytesToChars(SecretCache.take(PASSWORD), - StandardCharsets.UTF_8); + CryptoSecrets cryptoSecrets; - CryptoSecrets cryptoSecrets = getSecretsFromHex(hexKey, password); - CryptoManager cryptoManager = CryptoManagerProvider.getInstance(context); - - SecretsSetupService keySetupService = new SecretsSetupService( - getApplicationContext(), - cryptoManager, - cryptoSecrets - ); - - new KeySetupCompletionHandler(this, keySetupService, mode).handle(); - } catch (IllegalArgumentException e) { + try { + cryptoSecrets = getSecretsFromHex(hexKey, password); + cryptoSecrets.validate(); + } catch (IllegalArgumentException | IllegalStateException e) { Log.e(TAG, "Invalid key", e); showToastMessage(this, getString(R.string.invalid_key), Toast.LENGTH_SHORT); - } catch (CharacterCodingException e) { - throw new RuntimeException(e); + + return; } + + SecretsSetupService secretsSetupService = new SecretsSetupService( + getApplicationContext(), + cryptoManager, + cryptoSecrets + ); + + new KeySetupCompletionHandler(this, secretsSetupService, mode).handle(); } }; } + + private char[] getPasswordFromCache() { + try { + byte[] passwordBytes = requireNonNull(SecretCache.take(CACHE_KEY_PASSWORD), + "Password missing in secret cache"); + return bytesToChars(passwordBytes, StandardCharsets.UTF_8); + } catch (CharacterCodingException e) { + throw new RuntimeException(e); + } + } + + private void wipeSecrets() { + // Clearing only key field, not password, because reference to password is stored in cache + // and still could be used by SetupKeyActivity. SetupKeyActivity as a parent activity + // is responsible to clear password from cache. + if (hexKey != null && hexKey.length > 0) { + Arrays.fill(hexKey, '\0'); + } + + Editable hexKeyEditable = keyField.getText(); + hexKeyEditable.replace(0, hexKeyEditable.length(), ""); + + keyField.setText(""); + } } diff --git a/app/src/main/java/app/notesr/activity/security/KeyRecoveryActivity.java b/app/src/main/java/app/notesr/activity/security/KeyRecoveryActivity.java index f9dbecaf..7c081083 100644 --- a/app/src/main/java/app/notesr/activity/security/KeyRecoveryActivity.java +++ b/app/src/main/java/app/notesr/activity/security/KeyRecoveryActivity.java @@ -91,13 +91,15 @@ private View.OnClickListener applyButtonOnClick(EditText hexKeyField) { private void apply(EditText hexKeyField, char[] hexKey) throws IOException, NoSuchAlgorithmException { - byte[] keyBytes = getKeyBytesFromHex(Arrays.copyOf(hexKey, hexKey.length)); + char[] hexKeyCopy = Arrays.copyOf(hexKey, hexKey.length); + byte[] keyBytes = getKeyBytesFromHex(hexKeyCopy); Context context = getApplicationContext(); CryptoManager cryptoManager = CryptoManagerProvider.getInstance(context); if (cryptoManager.verifyKey(context, keyBytes)) { - SecretCache.put(AuthActivity.HEX_KEY, charsToBytes(hexKey, StandardCharsets.UTF_8)); + byte[] hexKeyBytes = charsToBytes(hexKey, StandardCharsets.UTF_8); + SecretCache.put(AuthActivity.HEX_KEY, hexKeyBytes); // The hex key has already been wiped by charsToBytes wipeSecretData(keyBytes, hexKeyField); diff --git a/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java b/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java index 194960ff..8e87ebf4 100644 --- a/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java +++ b/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java @@ -7,7 +7,6 @@ import static app.notesr.core.util.CharUtils.charsToBytes; -import android.app.Activity; import android.content.Context; import android.content.Intent; @@ -16,6 +15,9 @@ import java.util.Arrays; import app.notesr.BuildConfig; +import app.notesr.R; +import app.notesr.activity.ActivityBase; +import app.notesr.activity.DialogFactory; import app.notesr.activity.migration.MigrationActivity; import app.notesr.activity.note.list.NotesListActivity; import app.notesr.core.security.SecretCache; @@ -27,7 +29,7 @@ @RequiredArgsConstructor public final class KeySetupCompletionHandler { - private final Activity activity; + private final ActivityBase activity; private final SecretsSetupService secretsSetupService; private final KeySetupMode mode; @@ -46,7 +48,7 @@ private void proceedFirstRun() { Context context = activity.getApplicationContext(); Intent nextIntent = new Intent(context, NotesListActivity.class); - DataVersionManager dataVersionManager = new DataVersionManager(context); + var dataVersionManager = new DataVersionManager(context); int lastMigrationVersion = dataVersionManager.getCurrentVersion(); int currentDataSchemaVersion = BuildConfig.DATA_SCHEMA_VERSION; @@ -65,11 +67,23 @@ private void proceedFirstRun() { } private void proceedRegeneration() { + new DialogFactory(activity) + .getThemedAlertDialogBuilder(R.layout.dialog_re_encryption_warning) + .setTitle(R.string.warning) + .setPositiveButton(R.string.yes, + (dialog, which) -> onRegenerationConfirmed()) + .setNegativeButton(R.string.no, null) + .create() + .show(); + } + + private void onRegenerationConfirmed() { CryptoSecrets secrets = secretsSetupService.getCryptoSecrets(); + byte[] keyBytes = Arrays.copyOf(secrets.getKey(), secrets.getKey().length); + char[] password = Arrays.copyOf(secrets.getPassword(), secrets.getPassword().length); + try { - byte[] keyBytes = Arrays.copyOf(secrets.getKey(), secrets.getKey().length); - char[] password = Arrays.copyOf(secrets.getPassword(), secrets.getPassword().length); byte[] passwordBytes = charsToBytes(password, StandardCharsets.UTF_8); SecretCache.put(SecretsUpdateAndroidService.NEW_KEY, keyBytes); diff --git a/app/src/main/java/app/notesr/activity/security/LockOnClick.java b/app/src/main/java/app/notesr/activity/security/LockAction.java similarity index 66% rename from app/src/main/java/app/notesr/activity/security/LockOnClick.java rename to app/src/main/java/app/notesr/activity/security/LockAction.java index a7d736e1..0b733e67 100644 --- a/app/src/main/java/app/notesr/activity/security/LockOnClick.java +++ b/app/src/main/java/app/notesr/activity/security/LockAction.java @@ -11,23 +11,29 @@ import app.notesr.activity.ActivityBase; import app.notesr.core.security.SecretCache; import app.notesr.core.security.crypto.CryptoManager; -import app.notesr.core.security.crypto.CryptoManagerProvider; +import app.notesr.data.DatabaseProvider; +import lombok.RequiredArgsConstructor; -import java.util.function.Consumer; +@RequiredArgsConstructor +public final class LockAction { -public final class LockOnClick implements Consumer { - @Override - public void accept(ActivityBase activity) { + private final ActivityBase activity; + private final CryptoManager cryptoManager; + + public void lock() { Context context = activity.getApplicationContext(); Intent authActivityIntent = new Intent(context, AuthActivity.class) .putExtra(AuthActivity.EXTRA_MODE, AuthActivity.Mode.AUTHORIZATION.toString()); - CryptoManager cryptoManager = CryptoManagerProvider.getInstance(context); - cryptoManager.destroySecrets(); - - SecretCache.clear(); + clearSecrets(); activity.startActivity(authActivityIntent); activity.finish(); } + + private void clearSecrets() { + DatabaseProvider.close(); + cryptoManager.destroySecrets(); + SecretCache.clear(); + } } diff --git a/app/src/main/java/app/notesr/activity/security/SetupKeyActivity.java b/app/src/main/java/app/notesr/activity/security/SetupKeyActivity.java index fc9bfc32..8c679b2c 100644 --- a/app/src/main/java/app/notesr/activity/security/SetupKeyActivity.java +++ b/app/src/main/java/app/notesr/activity/security/SetupKeyActivity.java @@ -8,7 +8,6 @@ import static java.util.Objects.requireNonNull; import static app.notesr.core.util.ActivityUtils.copyToClipboard; -import static app.notesr.core.util.ActivityUtils.disableBackButton; import static app.notesr.core.util.ActivityUtils.showToastMessage; import static app.notesr.core.util.CharUtils.bytesToChars; import static app.notesr.core.util.CharUtils.charsToBytes; @@ -17,13 +16,18 @@ import android.content.Intent; import android.os.Bundle; import android.text.Editable; +import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; +import androidx.activity.OnBackPressedCallback; +import androidx.appcompat.app.ActionBar; + import java.nio.charset.CharacterCodingException; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import app.notesr.R; import app.notesr.activity.ActivityBase; @@ -37,7 +41,7 @@ @Getter public final class SetupKeyActivity extends ActivityBase { - public static final String PASSWORD = "password"; + public static final String CACHE_KEY_PASSWORD = "password"; public static final String EXTRA_MODE = "mode"; private static final int LOW_SCREEN_HEIGHT = 800; private static final float KEY_VIEW_TEXT_SIZE_FOR_LOW_SCREEN_HEIGHT = 16; @@ -53,16 +57,12 @@ protected void onCreate(Bundle savedInstanceState) { applyInsets(findViewById(R.id.main)); mode = KeySetupMode.valueOf(requireNonNull(getIntent().getStringExtra(EXTRA_MODE))); + ActionBar actionBar = requireNonNull(getSupportActionBar()); - if (mode == KeySetupMode.REGENERATION) { - disableBackButton(this); - } + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setTitle(R.string.key_setup); - try { - password = bytesToChars(SecretCache.take(PASSWORD), StandardCharsets.UTF_8); - } catch (CharacterCodingException e) { - throw new RuntimeException(e); - } + password = getPasswordFromCache(); Context context = getApplicationContext(); CryptoManager cryptoManager = CryptoManagerProvider.getInstance(context); @@ -85,6 +85,8 @@ protected void onCreate(Bundle savedInstanceState) { copyToClipboardButton.setOnClickListener(copyKeyButtonOnClick()); importButton.setOnClickListener(importKeyButtonOnClick()); nextButton.setOnClickListener(nextButtonOnClick()); + + getOnBackPressedDispatcher().addCallback(this, getOnBackPressedCallback()); } @Override @@ -92,6 +94,38 @@ protected boolean requiresSession() { return false; } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressedAction(); + finish(); + + return true; + } + + return super.onOptionsItemSelected(item); + } + + @Override + public void finish() { + wipeKeyView(); + super.finish(); + } + + private OnBackPressedCallback getOnBackPressedCallback() { + return new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + onBackPressedAction(); + finish(); + } + }; + } + + private void onBackPressedAction() { + SecretCache.removeIfExists(CACHE_KEY_PASSWORD); + } + private void adaptKeyView() { TextView keyView = findViewById(R.id.aesKeyHex); @@ -111,25 +145,34 @@ private View.OnClickListener copyKeyButtonOnClick() { private View.OnClickListener importKeyButtonOnClick() { return view -> { - Intent intent = new Intent(getApplicationContext(), ImportKeyActivity.class) - .putExtra(ImportKeyActivity.EXTRA_MODE, mode.toString()); + char[] passwordCopy = Arrays.copyOf(password, password.length); try { - byte[] passwordBytes = charsToBytes(password, StandardCharsets.UTF_8); - SecretCache.put(ImportKeyActivity.PASSWORD, passwordBytes); + byte[] passwordBytes = charsToBytes(passwordCopy, StandardCharsets.UTF_8); + SecretCache.put(ImportKeyActivity.CACHE_KEY_PASSWORD, passwordBytes); } catch (CharacterCodingException e) { throw new RuntimeException(e); } + Intent intent = new Intent(getApplicationContext(), ImportKeyActivity.class) + .putExtra(ImportKeyActivity.EXTRA_MODE, mode.toString()); startActivity(intent); }; } + private char[] getPasswordFromCache() { + try { + byte[] passwordBytes = requireNonNull(SecretCache.take(CACHE_KEY_PASSWORD), + "Password missing in secret cache"); + return bytesToChars(passwordBytes, StandardCharsets.UTF_8); + } catch (CharacterCodingException e) { + throw new RuntimeException(e); + } + } + private View.OnClickListener nextButtonOnClick() { - return view -> { - wipeKeyView(); - new KeySetupCompletionHandler(this, keySetupService, mode).handle(); - }; + var handler = new KeySetupCompletionHandler(this, keySetupService, mode); + return view -> handler.handle(); } private void wipeKeyView() { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index da303f2d..f6baaf31 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -23,6 +23,7 @@ Enter your private key here… Blocked! Key Recovery + Key Setup Apply Wrong key! Invalid key