commit ea9d77ee32abd692ce36c2facc6d1e9a8142f9e0
parent 7f181b47a59fd214ca52f86c4e461bd52aefa422
Author: triesap <tyson@radroots.org>
Date: Sat, 21 Mar 2026 16:58:34 +0000
android: add device-reset action for local state
- expose `Reset This Device` from the android backend in this slice
- remove all local identities and the android accounts file before returning the app to setup
- add android tests for clearing every local identity and deleting the accounts file
- keep the change scoped to the android launcher crate with the existing shared home reset contract
Diffstat:
1 file changed, 105 insertions(+), 0 deletions(-)
diff --git a/crates/android/src/lib.rs b/crates/android/src/lib.rs
@@ -18,6 +18,8 @@ use radroots_nostr_accounts::prelude::RadrootsNostrAccountRecord;
use radroots_nostr_accounts::prelude::RadrootsNostrAccountsManager;
#[cfg(any(target_os = "android", test))]
use radroots_nostr_accounts::prelude::RadrootsNostrSelectedAccountStatus;
+#[cfg(any(target_os = "android", test))]
+use std::path::Path;
#[cfg(target_os = "android")]
use winit::platform::android::activity::AndroidApp;
@@ -99,6 +101,36 @@ impl RadrootsAppBackend for AndroidBackend {
Ok(None)
}
}
+
+ fn home_reset_action_state(&self) -> Option<HomeActionState> {
+ #[cfg(target_os = "android")]
+ {
+ return Some(HomeActionState {
+ label: "Reset This Device".to_owned(),
+ enabled: true,
+ pending: false,
+ });
+ }
+
+ #[cfg(not(target_os = "android"))]
+ {
+ None
+ }
+ }
+
+ fn request_home_reset_action(&self) -> Result<Option<IdentityGateState>, String> {
+ #[cfg(target_os = "android")]
+ {
+ let manager = Self::accounts_manager()?;
+ let accounts_path = storage::accounts_path()?;
+ return Self::reset_local_device_state(&manager, accounts_path.as_path()).map(Some);
+ }
+
+ #[cfg(not(target_os = "android"))]
+ {
+ Ok(None)
+ }
+ }
}
#[cfg(any(target_os = "android", test))]
@@ -179,6 +211,43 @@ impl AndroidBackend {
.map_err(|source| source.to_string())?;
Self::identity_state_from_manager(manager)
}
+
+ fn remove_all_local_identities(
+ manager: &RadrootsNostrAccountsManager,
+ ) -> Result<IdentityGateState, String> {
+ let account_ids = manager
+ .list_accounts()
+ .map_err(|source| source.to_string())?
+ .into_iter()
+ .map(|record| record.account_id)
+ .collect::<Vec<_>>();
+
+ for account_id in account_ids {
+ manager
+ .remove_account(&account_id)
+ .map_err(|source| source.to_string())?;
+ }
+
+ Self::identity_state_from_manager(manager)
+ }
+
+ fn remove_accounts_file_if_present(accounts_path: &Path) -> Result<(), String> {
+ match std::fs::remove_file(accounts_path) {
+ Ok(()) => Ok(()),
+ Err(source) if source.kind() == std::io::ErrorKind::NotFound => Ok(()),
+ Err(source) => Err(format!("failed to remove android accounts file: {source}")),
+ }
+ }
+
+ #[cfg(target_os = "android")]
+ fn reset_local_device_state(
+ manager: &RadrootsNostrAccountsManager,
+ accounts_path: &Path,
+ ) -> Result<IdentityGateState, String> {
+ let state = Self::remove_all_local_identities(manager)?;
+ Self::remove_accounts_file_if_present(accounts_path)?;
+ Ok(state)
+ }
}
#[cfg(any(target_os = "android", test))]
@@ -323,4 +392,40 @@ mod tests {
None
);
}
+
+ #[test]
+ fn remove_all_local_identities_clears_every_account() {
+ let manager = RadrootsNostrAccountsManager::new_in_memory();
+
+ manager
+ .generate_identity(Some("first".into()), true)
+ .expect("generate first");
+ manager
+ .generate_identity(Some("second".into()), false)
+ .expect("generate second");
+
+ let state = AndroidBackend::remove_all_local_identities(&manager).expect("reset state");
+
+ assert_eq!(state, IdentityGateState::Missing);
+ assert_eq!(manager.list_accounts().expect("list accounts").len(), 0);
+ assert_eq!(manager.selected_account_id().expect("selected"), None);
+ }
+
+ #[test]
+ fn remove_accounts_file_if_present_deletes_existing_file() {
+ let unique = format!(
+ "radroots-android-reset-{}-{}.json",
+ std::process::id(),
+ std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .expect("system time")
+ .as_nanos()
+ );
+ let path = std::env::temp_dir().join(unique);
+ std::fs::write(&path, b"{}").expect("write accounts file");
+
+ AndroidBackend::remove_accounts_file_if_present(path.as_path()).expect("remove file");
+
+ assert!(!path.exists());
+ }
}